func sqlColExpr(name string, dbtype string) string {
	name = strconv.Quote(name)
	switch dbtype {
	case forceTextTSVECTOR:
		return fmt.Sprintf("%s::text", name)
	}
	if toJSONTypeFromPG(dbtype) == JSONTypeString {
		return fmt.Sprintf("%s::text", name)
	}
	return name
}
makeSQLColExpr casts a column to text if type is unknown to PGX
package data
import (
	"fmt"
	"strconv"
	"strings"
	log "github.com/sirupsen/logrus"
)
const forceTextTSVECTOR = "tsvector"
const sqlTables = `SELECT
	Format('%s.%s', n.nspname, c.relname) AS id,
	n.nspname AS schema,
	c.relname AS table,
	coalesce(d.description, '') AS description,
	a.attname AS geometry_column,
	postgis_typmod_srid(a.atttypmod) AS srid,
	postgis_typmod_type(a.atttypmod) AS geometry_type,
	coalesce(ia.attname, '') AS id_column,
	(
		SELECT array_agg(ARRAY[sa.attname, st.typname, coalesce(da.description,''), sa.attnum::text]::text[] ORDER BY sa.attnum)
		FROM pg_attribute sa
		JOIN pg_type st ON sa.atttypid = st.oid
		LEFT JOIN pg_description da ON (c.oid = da.objoid and sa.attnum = da.objsubid)
		WHERE sa.attrelid = c.oid
		AND sa.attnum > 0
		AND NOT sa.attisdropped
		AND st.typname NOT IN ('geometry', 'geography')
	) AS props
FROM pg_class c
JOIN pg_namespace n ON (c.relnamespace = n.oid)
JOIN pg_attribute a ON (a.attrelid = c.oid)
JOIN pg_type t ON (a.atttypid = t.oid)
LEFT JOIN pg_description d ON (c.oid = d.objoid AND d.objsubid = 0)
LEFT JOIN pg_index i ON (c.oid = i.indrelid AND i.indisprimary
AND i.indnatts = 1)
LEFT JOIN pg_attribute ia ON (ia.attrelid = i.indexrelid)
LEFT JOIN pg_type it ON (ia.atttypid = it.oid AND it.typname in ('int2', 'int4', 'int8'))
WHERE c.relkind IN ('r', 'v', 'm', 'p', 'f')
AND t.typname = 'geometry'
AND has_table_privilege(c.oid, 'select')
AND postgis_typmod_srid(a.atttypmod) > 0
ORDER BY id
`
const sqlFunctions = `WITH
proargs AS (
	SELECT p.oid,
		generate_subscripts(p.proallargtypes, 1) AS argorder,
		unnest(p.proallargtypes) AS argtype,
		unnest(p.proargmodes) AS argmode,
		unnest(p.proargnames) AS argname
	FROM pg_proc p
	JOIN pg_namespace n ON (p.pronamespace = n.oid)
	WHERE n.nspname = 'postgisftw'
		AND array_length(p.proargnames, 1) = array_length(p.proargmodes, 1)
		AND array_length(p.proargmodes, 1) = array_length(p.proallargtypes, 1)
		AND has_schema_privilege(n.oid, 'usage')
		AND has_function_privilege(Format('%s.%s(%s)', n.nspname, p.proname, oidvectortypes(proargtypes)), 'execute')
),
proargarrays AS (
	SELECT p.oid,
		array_agg(p.argname ORDER BY argorder) FILTER (WHERE p.argmode = 'i') AS arginnames,
		array_agg(t.typname ORDER BY argorder) FILTER (WHERE p.argmode = 'i') AS argintypes,
		array_agg(p.argname ORDER BY argorder) FILTER (WHERE p.argmode IN ('o','t')) AS argoutnames,
		array_agg(t.typname ORDER BY argorder) FILTER (WHERE p.argmode IN ('o','t')) AS argouttypes
	FROM proargs p
	JOIN pg_type t ON (p.argtype = t.oid)
	GROUP BY 1
)
SELECT
	p.proname AS id,
	n.nspname AS schema,
	p.proname AS function,
	coalesce(d.description, '') AS description,
	aa.arginnames AS input_names,
	aa.argintypes AS input_types,
	coalesce(string_to_array(regexp_replace(pg_get_expr(p.proargdefaults, 0::Oid),
		'''([a-zA-Z0-9_\-\.]+)''::\w+', '\1', 'g'),', '), ARRAY[]::text[]) AS argdefaults,
	argoutnames AS output_names,
    argouttypes AS output_types
FROM pg_proc p
JOIN pg_namespace n ON (p.pronamespace = n.oid)
JOIN proargarrays aa ON (p.oid = aa.oid)
LEFT JOIN pg_description d ON (p.oid = d.objoid)
ORDER BY id`
const sqlFmtExtentEst = `SELECT ST_XMin(ext.geom) AS xmin, ST_YMin(ext.geom) AS ymin, ST_XMax(ext.geom) AS xmax, ST_YMax(ext.geom) AS ymax
FROM ( SELECT ST_Transform(ST_SetSRID(ST_EstimatedExtent('%s', '%s', '%s'), %d), 4326) AS geom ) AS ext;`
func sqlExtentEstimated(tbl *Table) string {
	return fmt.Sprintf(sqlFmtExtentEst, tbl.Schema, tbl.Table, tbl.GeometryColumn, tbl.Srid)
}
const sqlFmtExtentExact = `SELECT ST_XMin(ext.geom) AS xmin, ST_YMin(ext.geom) AS ymin, ST_XMax(ext.geom) AS xmax, ST_YMax(ext.geom) AS ymax
FROM (SELECT coalesce( ST_Transform(ST_SetSRID(ST_Extent("%s"), %d), 4326),	ST_MakeEnvelope(-180, -90, 180, 90, 4326)) AS geom FROM "%s"."%s" ) AS ext;`
func sqlExtentExact(tbl *Table) string {
	return fmt.Sprintf(sqlFmtExtentExact, tbl.GeometryColumn, tbl.Srid, tbl.Schema, tbl.Table)
}
const sqlFmtFeatures = "SELECT %v %v FROM \"%s\".\"%s\" %v %v %v %s;"
func sqlFeatures(tbl *Table, param *QueryParam) (string, []interface{}) {
	geomCol := sqlGeomCol(tbl.GeometryColumn, param)
	propCols := sqlColList(param.Columns, tbl.DbTypes, true)
	bboxFilter := sqlBBoxFilter(tbl, param.Bbox)
	attrFilter, attrVals := sqlAttrFilter(param.Filter)
	sqlWhere := sqlWhere(bboxFilter, attrFilter)
	sqlGroupBy := sqlGroupBy(param.GroupBy)
	sqlOrderBy := sqlOrderBy(param.SortBy)
	sqlLimitOffset := sqlLimitOffset(param.Limit, param.Offset)
	sql := fmt.Sprintf(sqlFmtFeatures, geomCol, propCols, tbl.Schema, tbl.Table, sqlWhere, sqlGroupBy, sqlOrderBy, sqlLimitOffset)
	return sql, attrVals
}
func sqlColList(names []string, dbtypes map[string]string, addLeadingComma bool) string {
	if len(names) == 0 {
		return ""
	}
	var cols []string
	for _, col := range names {
		colExpr := sqlColExpr(col, dbtypes[col])
		cols = append(cols, colExpr)
	}
	colsStr := strings.Join(cols, ",")
	if addLeadingComma {
		return ", " + colsStr
	}
	return colsStr
}