Skip to content

Commit 735b1c3

Browse files
committed
[Feature][PostgreSQL] Compile expression functions
1 parent cd970f2 commit 735b1c3

File tree

5 files changed

+228
-2
lines changed

5 files changed

+228
-2
lines changed

src/core/qgssqlexpressioncompiler.cpp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,13 +319,60 @@ QgsSqlExpressionCompiler::Result QgsSqlExpressionCompiler::compileNode( const Qg
319319
}
320320

321321
case QgsExpression::ntFunction:
322+
{
323+
const QgsExpression::NodeFunction* n = static_cast<const QgsExpression::NodeFunction*>( node );
324+
QgsExpression::Function* fd = QgsExpression::Functions()[n->fnIndex()];
325+
326+
// get sql function to compile node expression
327+
QString nd = sqlFunctionFromFunctionName( fd->name() );
328+
// if no sql function the node can't be compiled
329+
if ( nd.isEmpty() )
330+
return Fail;
331+
332+
// compile arguments
333+
QStringList args;
334+
Result inResult = Complete;
335+
Q_FOREACH ( const QgsExpression::Node* ln, n->args()->list() )
336+
{
337+
QString s;
338+
Result r = compileNode( ln, s );
339+
if ( r == Complete || r == Partial )
340+
{
341+
args << s;
342+
if ( r == Partial )
343+
inResult = Partial;
344+
}
345+
else
346+
return r;
347+
}
348+
349+
// update arguments to be adapted to SQL function
350+
args = sqlArgumentsFromFunctionName( fd->name(), args );
351+
352+
// build result
353+
result = QStringLiteral( "%1(%2)" ).arg( nd, args.join( ',' ) );
354+
return inResult == Partial ? Partial : Complete;
355+
}
356+
322357
case QgsExpression::ntCondition:
323358
break;
324359
}
325360

326361
return Fail;
327362
}
328363

364+
QString QgsSqlExpressionCompiler::sqlFunctionFromFunctionName( const QString& fnName ) const
365+
{
366+
Q_UNUSED( fnName );
367+
return QString();
368+
}
369+
370+
QStringList QgsSqlExpressionCompiler::sqlArgumentsFromFunctionName( const QString& fnName, const QStringList& fnArgs ) const
371+
{
372+
Q_UNUSED( fnName );
373+
return QStringList( fnArgs );
374+
}
375+
329376
bool QgsSqlExpressionCompiler::nodeIsNullLiteral( const QgsExpression::Node* node ) const
330377
{
331378
if ( node->nodeType() != QgsExpression::ntLiteral )

src/core/qgssqlexpressioncompiler.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,21 @@ class CORE_EXPORT QgsSqlExpressionCompiler
9393
*/
9494
virtual Result compileNode( const QgsExpression::Node* node, QString& str );
9595

96+
/** Return the SQL function for the expression function.
97+
* Derived classes should override this to help compile functions
98+
* @param fnName expression function name
99+
* @returns the SQL function name
100+
*/
101+
virtual QString sqlFunctionFromFunctionName( const QString& fnName ) const;
102+
103+
/** Return the Arguments for SQL function for the expression function.
104+
* Derived classes should override this to help compile functions
105+
* @param fnName expression function name
106+
* @param fnArgs arguments from expression
107+
* @returns the arguments updated for SQL Function
108+
*/
109+
virtual QStringList sqlArgumentsFromFunctionName( const QString& fnName, const QStringList& fnArgs ) const;
110+
96111
QString mResult;
97112
QgsFields mFields;
98113

src/providers/postgres/qgspostgresexpressioncompiler.cpp

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@
1818

1919
QgsPostgresExpressionCompiler::QgsPostgresExpressionCompiler( QgsPostgresFeatureSource* source )
2020
: QgsSqlExpressionCompiler( source->mFields )
21+
, mGeometryColumn( source->mGeometryColumn )
22+
, mSpatialColType( source->mSpatialColType )
23+
, mDetectedGeomType( source->mDetectedGeomType )
24+
, mRequestedGeomType( source->mRequestedGeomType )
25+
, mRequestedSrid( source->mRequestedSrid )
26+
, mDetectedSrid( source->mDetectedSrid )
2127
{
2228
}
2329

@@ -32,3 +38,150 @@ QString QgsPostgresExpressionCompiler::quotedValue( const QVariant& value, bool&
3238
return QgsPostgresConn::quotedValue( value );
3339
}
3440

41+
static const QMap<QString, QString>& functionNamesSqlFunctionsMap()
42+
{
43+
static QMap<QString, QString> fnNames;
44+
if ( fnNames.isEmpty() )
45+
{
46+
fnNames =
47+
{
48+
{ "sqrt", "sqrt" },
49+
{ "radians", "radians" },
50+
{ "degrees", "degrees" },
51+
{ "abs", "abs" },
52+
{ "cos", "cos" },
53+
{ "sin", "sin" },
54+
{ "tan", "tan" },
55+
{ "acos", "acos" },
56+
{ "asin", "asin" },
57+
{ "atan", "atan" },
58+
{ "atan2", "atan2" },
59+
{ "exp", "exp" },
60+
{ "ln", "ln" },
61+
{ "log", "log" },
62+
{ "log10", "log" },
63+
{ "round", "round" },
64+
{ "floor", "floor" },
65+
{ "ceil", "ceil" },
66+
{ "pi", "pi" },
67+
// geometry functions
68+
//{ "azimuth", "ST_Azimuth" },
69+
{ "x", "ST_X" },
70+
{ "y", "ST_Y" },
71+
//{ "z", "ST_Z" },
72+
//{ "m", "ST_M" },
73+
{ "x_min", "ST_XMin" },
74+
{ "y_min", "ST_YMin" },
75+
{ "x_max", "ST_XMax" },
76+
{ "y_max", "ST_YMax" },
77+
{ "area", "ST_Area" },
78+
{ "perimeter", "ST_Perimeter" },
79+
{ "relate", "ST_Relate" },
80+
{ "disjoint", "ST_Disjoint" },
81+
{ "intersects", "ST_Intersects" },
82+
//{ "touches", "ST_Touches" },
83+
{ "crosses", "ST_Crosses" },
84+
{ "contains", "ST_Contains" },
85+
{ "overlaps", "ST_Overlaps" },
86+
{ "within", "ST_Within" },
87+
{ "translate", "ST_Translate" },
88+
{ "buffer", "ST_Buffer" },
89+
{ "centroid", "ST_Centroid" },
90+
{ "point_on_surface", "ST_PointOnSurface" },
91+
//{ "reverse", "ST_Reverse" },
92+
//{ "is_closed", "ST_IsClosed" },
93+
//{ "convex_hull", "ST_ConvexHull" },
94+
//{ "difference", "ST_Difference" },
95+
{ "distance", "ST_Distance" },
96+
//{ "intersection", "ST_Intersection" },
97+
//{ "sym_difference", "ST_SymDifference" },
98+
//{ "combine", "ST_Union" },
99+
//{ "union", "ST_Union" },
100+
{ "geom_from_wkt", "ST_GeomFromText" },
101+
{ "geom_from_gml", "ST_GeomFromGML" }
102+
};
103+
}
104+
return fnNames;
105+
}
106+
107+
QString QgsPostgresExpressionCompiler::sqlFunctionFromFunctionName( const QString& fnName ) const
108+
{
109+
return functionNamesSqlFunctionsMap().value( fnName, QString() );
110+
}
111+
112+
QStringList QgsPostgresExpressionCompiler::sqlArgumentsFromFunctionName( const QString& fnName, const QStringList& fnArgs ) const
113+
{
114+
QStringList args( fnArgs );
115+
if ( fnName == "geom_from_wkt" )
116+
{
117+
args << ( mRequestedSrid.isEmpty() ? mDetectedSrid : mRequestedSrid );
118+
}
119+
else if ( fnName == "geom_from_gml" )
120+
{
121+
args << ( mRequestedSrid.isEmpty() ? mDetectedSrid : mRequestedSrid );
122+
}
123+
else if ( fnName == "x" || fnName == "y" )
124+
{
125+
args = QStringList( QStringLiteral( "ST_Centroid(%1)" ).arg( args[0] ) );
126+
}
127+
else if ( fnName == "buffer" && args.length() == 2 )
128+
{
129+
args << "8";
130+
}
131+
// x and y functions have to be adapted
132+
return args;
133+
}
134+
135+
QgsSqlExpressionCompiler::Result QgsPostgresExpressionCompiler::compileNode( const QgsExpression::Node* node, QString& result )
136+
{
137+
switch ( node->nodeType() )
138+
{
139+
case QgsExpression::ntFunction:
140+
{
141+
const QgsExpression::NodeFunction* n = static_cast<const QgsExpression::NodeFunction*>( node );
142+
143+
QgsExpression::Function* fd = QgsExpression::Functions()[n->fnIndex()];
144+
if ( fd->name() == "$geometry" )
145+
{
146+
result = quotedIdentifier( mGeometryColumn );
147+
return Complete;
148+
}
149+
/*
150+
* These methods are tricky
151+
* QGIS expression versions of these return ellipsoidal measurements
152+
* based on the project settings, and also convert the result to the
153+
* units specified in project properties.
154+
else if ( fd->name() == "$area" )
155+
{
156+
result = QStringLiteral( "ST_Area(%1)" ).arg( quotedIdentifier( mGeometryColumn ) );
157+
return Complete;
158+
}
159+
else if ( fd->name() == "$length" )
160+
{
161+
result = QStringLiteral( "ST_Length(%1)" ).arg( quotedIdentifier( mGeometryColumn ) );
162+
return Complete;
163+
}
164+
else if ( fd->name() == "$perimeter" )
165+
{
166+
result = QStringLiteral( "ST_Perimeter(%1)" ).arg( quotedIdentifier( mGeometryColumn ) );
167+
return Complete;
168+
}
169+
else if ( fd->name() == "$x" )
170+
{
171+
result = QStringLiteral( "ST_X(%1)" ).arg( quotedIdentifier( mGeometryColumn ) );
172+
return Complete;
173+
}
174+
else if ( fd->name() == "$y" )
175+
{
176+
result = QStringLiteral( "ST_Y(%1)" ).arg( quotedIdentifier( mGeometryColumn ) );
177+
return Complete;
178+
}
179+
*/
180+
}
181+
182+
default:
183+
return QgsSqlExpressionCompiler::compileNode( node, result );
184+
}
185+
186+
return Fail;
187+
}

src/providers/postgres/qgspostgresexpressioncompiler.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
#include "qgssqlexpressioncompiler.h"
2020
#include "qgsexpression.h"
21+
#include "qgspostgresconn.h"
2122
#include "qgspostgresfeatureiterator.h"
2223

2324
class QgsPostgresExpressionCompiler : public QgsSqlExpressionCompiler
@@ -30,6 +31,16 @@ class QgsPostgresExpressionCompiler : public QgsSqlExpressionCompiler
3031

3132
virtual QString quotedIdentifier( const QString& identifier ) override;
3233
virtual QString quotedValue( const QVariant& value, bool& ok ) override;
34+
virtual Result compileNode( const QgsExpression::Node* node, QString& str ) override;
35+
virtual QString sqlFunctionFromFunctionName( const QString& fnName ) const override;
36+
virtual QStringList sqlArgumentsFromFunctionName( const QString& fnName, const QStringList& fnArgs ) const override;
37+
38+
QString mGeometryColumn;
39+
QgsPostgresGeometryColumnType mSpatialColType;
40+
QgsWkbTypes::Type mDetectedGeomType;
41+
QgsWkbTypes::Type mRequestedGeomType;
42+
QString mRequestedSrid;
43+
QString mDetectedSrid;
3344
};
3445

3546
#endif // QGSPOSTGRESEXPRESSIONCOMPILER_H

tests/src/python/test_provider_postgres.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ def disableCompiler(self):
9494
QSettings().setValue('/qgis/compileExpressions', False)
9595

9696
def uncompiledFilters(self):
97-
return set(['intersects($geometry,geom_from_wkt( \'Polygon ((-72.2 66.1, -65.2 66.1, -65.2 72.0, -72.2 72.0, -72.2 66.1))\'))'])
97+
return set([])
9898

9999
def partiallyCompiledFilters(self):
100100
return set([])
@@ -664,7 +664,7 @@ def disableCompiler(self):
664664
QSettings().setValue('/qgis/compileExpressions', False)
665665

666666
def uncompiledFilters(self):
667-
return set(['intersects($geometry,geom_from_wkt( \'Polygon ((-72.2 66.1, -65.2 66.1, -65.2 72.0, -72.2 72.0, -72.2 66.1))\'))'])
667+
return set([])
668668

669669
def partiallyCompiledFilters(self):
670670
return set([])

0 commit comments

Comments
 (0)