-
Notifications
You must be signed in to change notification settings - Fork 18
/
makeSql.js
138 lines (119 loc) · 5.59 KB
/
makeSql.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
"use strict";
// Functions to create Grainstore SQL queries.
// Grainstore expects to receive a subquery it can plug into another statement.
// Postgres requires subqueries to be named.
// A single request to the tiler will have two separate components
// that are used to build a single query. This is done because the
// specification format for these two operations is significantly
// different. By processing them separately in the tiler, the
// client-side code of the application sending requests can build
// these parameters more simply.
// * filterQuery: this contains
// model/value clauses that easily mapped to predicates in SQL WHERE
// clauses
// * displayQuery: these contain models to show, which then
// get grouped into an additional predicate in the SQL where
// clause. This was created as a sidecare to filterQuery because the
// case of ANDing a plot predicate with a null tree value is
// prohibitively complicated using filterQuery syntax.
// Performance note: We tried using ST_SnapToGrid to reduce the number of trees rendered.
// While rendering does get faster, database queries slow down by a factor of at least five.
// That particularly hurts in production where we have just one DB server and four renderers.
var _ = require('underscore');
var util = require('util');
var filterObjectToWhere = require('./filterObjectToWhere');
var displayFiltersToWhere = require('./displayFiltersToWhere');
var filtersToTables = require('./filtersToTables');
var addDefaultsToFilter = require('./addDefaultsToFilter');
var config = require('./config');
var units = require('./units');
var utils = require('./filterObjectUtils');
// Create a SQL query to return info about map features.
// Assumes that instanceid is an integer, ready to be plugged
// directly into SQL
function makeSqlForMapFeatures(filterString, displayString, restrictFeatureString, instanceid,
zoom, isUtfGridRequest, isPolygonRequest, instanceConfig) {
var geom_spec = config.sqlForMapFeatures.fields.geom,
geom_field = isPolygonRequest ? geom_spec.polygon : geom_spec.point,
parsedFilterObject = filterString ? JSON.parse(filterString) : {},
displayFilters = displayString ? JSON.parse(displayString) : undefined,
restrictFeatureFilters = restrictFeatureString ? JSON.parse(restrictFeatureString) : undefined,
filterObjectWithDefaults = addDefaultsToFilter(parsedFilterObject, zoom, isPolygonRequest),
filterObject = units.convertFilterUnits(filterObjectWithDefaults, instanceConfig),
tables = filtersToTables(filterObject, displayFilters, isPolygonRequest, isUtfGridRequest),
where = '',
displayClause = displayFiltersToWhere(displayFilters, restrictFeatureFilters, displayPlotsOnly(filterObject)),
filterClause = filterObjectToWhere(filterObject),
instanceClause = (instanceid ? _.template(config.sqlForMapFeatures.where.instance)({instanceid: instanceid}) : null);
function addToWhere(clause) {
return where ? '( ' + clause + ' ) AND ' + where : clause;
}
if (filterClause) {
where = filterClause;
// Because some searches (e.g. on photos and udf's) join to other tables,
// add DISTINCT so we only get one row.
geom_field = util.format('DISTINCT(%s)', geom_field);
} else if (showingPlotsAndTrees(displayFilters)) {
var showAtZoom = _.template(config.showAtZoomSql)({zoom: zoom});
where = addToWhere(showAtZoom);
}
if (displayClause) {
where = addToWhere(displayClause);
}
if (instanceClause) {
where = addToWhere(instanceClause);
}
if (where) {
where = 'WHERE ' + where;
}
var otherFields;
if (isUtfGridRequest) {
otherFields = config.sqlForMapFeatures.fields.utfGrid;
} else if (isPolygonRequest) {
otherFields = config.sqlForMapFeatures.fields.polygon;
} else {
otherFields = config.sqlForMapFeatures.fields.base;
}
geom_field = util.format("%s AS %s",
geom_field, config.customDbFieldNames.geom);
return _.template(
'SELECT <%= fields %> FROM <%= tables %> <%= where %>'
)({
fields: geom_field + ', ' + otherFields,
tables: tables.sql,
where: where
});
}
// If there are trees referenced in the filter object, narrow the
// display filters to only tree display filters.
function displayPlotsOnly(filterObject) {
var isTreeFilterObject = function(s) {return s.substring(0, 4) === 'tree'; };
var treeKeys = _.filter(utils.filterObjectKeys(filterObject), isTreeFilterObject);
return treeKeys.length > 0;
}
function showingPlotsAndTrees(displayFilters) {
var isEmpty = (displayFilters === undefined || displayFilters === null),
hasPlotAndTree = _.contains(displayFilters, 'Plot') && _.contains(displayFilters, 'Tree');
return isEmpty || hasPlotAndTree;
}
// Create a SQL query to return info about boundaries.
// Assumes that instanceid is an integer, ready to be plugged
// directly into SQL
function makeSqlForBoundaries(instanceid) {
return _.template(config.boundaryGrainstoreSql)({
instanceid: instanceid
});
}
function makeSqlForCanopyBoundaries(instanceid, canopy_min, canopy_max, category) {
return _.template(config.canopyBoundarySql)({
instanceid: instanceid,
canopy_min: canopy_min,
canopy_max: canopy_max,
category: category
});
}
exports = module.exports = {
makeSqlForMapFeatures: makeSqlForMapFeatures,
makeSqlForCanopyBoundaries: makeSqlForCanopyBoundaries,
makeSqlForBoundaries: makeSqlForBoundaries
};