Skip to content

Commit 36714d5

Browse files
frugardcNathanW2
authored andcommitted
[FEATURE] [mssql] Save styles to the database in MSSQL
Adding the functions to save styles in an mssql database. Creates tables, etc to support default and listing styles.
1 parent d47fe0f commit 36714d5

3 files changed

Lines changed: 352 additions & 1 deletion

File tree

‎src/providers/mssql/qgsmssqlprovider.cpp‎

Lines changed: 344 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,32 @@ void QgsMssqlProvider::loadFields()
475475
}
476476
}
477477

478+
QString QgsMssqlProvider::quotedValue( const QVariant& value )
479+
{
480+
if ( value.isNull() )
481+
return "NULL";
482+
483+
switch ( value.type() )
484+
{
485+
case QVariant::Int:
486+
case QVariant::LongLong:
487+
case QVariant::Double:
488+
return value.toString();
489+
490+
case QVariant::Bool:
491+
return value.toBool() ? "1" : "0";
492+
493+
default:
494+
case QVariant::String:
495+
QString v = value.toString();
496+
v.replace( '\'', "''" );
497+
if ( v.contains( '\\' ) )
498+
return v.replace( '\\', "\\\\" ).prepend( "N'" ).append( '\'' );
499+
else
500+
return v.prepend( '\'' ).append( '\'' );
501+
}
502+
}
503+
478504
QVariant QgsMssqlProvider::defaultValue( int fieldId )
479505
{
480506
if ( mDefaultValues.contains( fieldId ) )
@@ -1934,3 +1960,321 @@ QGISEXTERN QgsVectorLayerImport::ImportError createEmptyLayer(
19341960
oldToNewAttrIdxMap, errorMessage, options
19351961
);
19361962
}
1963+
QGISEXTERN bool saveStyle( const QString& uri, const QString& qmlStyle, const QString& sldStyle,
1964+
const QString& styleName, const QString& styleDescription,
1965+
const QString& uiFileContent, bool useAsDefault, QString& errCause )
1966+
{
1967+
QgsDataSourceURI dsUri( uri );
1968+
// connect to database
1969+
QSqlDatabase mDatabase = QgsMssqlProvider::GetDatabase( dsUri.service(), dsUri.host(), dsUri.database(), dsUri.username(), dsUri.password() );
1970+
1971+
if ( !QgsMssqlProvider::OpenDatabase( mDatabase ) )
1972+
{
1973+
QgsDebugMsg( "Error connecting to database" );
1974+
QgsDebugMsg( mDatabase.lastError().text() );
1975+
return false;
1976+
}
1977+
1978+
QSqlQuery query = QSqlQuery( mDatabase );
1979+
query.setForwardOnly( true );
1980+
if ( !query.exec( QString( "SELECT COUNT(*) FROM information_schema.tables WHERE table_name= N'layer_styles'" ) ) )
1981+
{
1982+
QString msg = query.lastError().text();
1983+
QgsDebugMsg( msg );
1984+
return false;
1985+
}
1986+
if ( query.isActive() && query.next() && query.value( 0 ).toInt() == 0 )
1987+
{
1988+
QgsDebugMsg( "Need to create styles table" );
1989+
bool execOk = query.exec( QString( "CREATE TABLE [dbo].[layer_styles]("
1990+
"[id] int IDENTITY(1,1) PRIMARY KEY,"
1991+
"[f_table_catalog] [varchar](1024) NULL,"
1992+
"[f_table_schema] [varchar](1024) NULL,"
1993+
"[f_table_name] [varchar](1024) NULL,"
1994+
"[f_geometry_column] [varchar](1024) NULL,"
1995+
"[styleName] [varchar](1024) NULL,"
1996+
"[styleQML] [text] NULL,"
1997+
"[styleSLD] [text] NULL,"
1998+
"[useAsDefault] [int] NULL,"
1999+
"[description] [text] NULL,"
2000+
"[owner] [varchar](1024) NULL,"
2001+
"[ui] [text] NULL,"
2002+
"[update_time] [datetime] NULL"
2003+
") ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]" ));
2004+
if ( !execOk )
2005+
{
2006+
errCause = QObject::tr( "Unable to save layer style. It's not possible to create the destination table on the database. Maybe this is due to table permissions. Please contact your database admin" );
2007+
return false;
2008+
}
2009+
query.finish();
2010+
query.clear();
2011+
}
2012+
2013+
QString uiFileColumn;
2014+
QString uiFileValue;
2015+
if ( !uiFileContent.isEmpty() )
2016+
{
2017+
uiFileColumn = ",ui";
2018+
uiFileValue = QString( ",XMLPARSE(DOCUMENT %1)" ).arg( uiFileContent );
2019+
}
2020+
QgsDebugMsg( "Ready to insert new style" );
2021+
// Note: in the construction of the INSERT and UPDATE strings the qmlStyle and sldStyle values
2022+
// can contain user entered strings, which may themselves include %## values that would be
2023+
// replaced by the QString.arg function. To ensure that the final SQL string is not corrupt these
2024+
// two values are both replaced in the final .arg call of the string construction.
2025+
2026+
QString sql = QString( "INSERT INTO layer_styles"
2027+
"(f_table_catalog,f_table_schema,f_table_name,f_geometry_column,styleName,styleQML,styleSLD,useAsDefault,description,owner%11"
2028+
") VALUES ("
2029+
"%1,%2,%3,%4,%5,%6,%7,%8,%9,%10%12"
2030+
")" )
2031+
.arg( QgsMssqlProvider::quotedValue( dsUri.database() ) )
2032+
.arg( QgsMssqlProvider::quotedValue( dsUri.schema() ) )
2033+
.arg( QgsMssqlProvider::quotedValue( dsUri.table() ) )
2034+
.arg( QgsMssqlProvider::quotedValue( dsUri.geometryColumn() ) )
2035+
.arg( QgsMssqlProvider::quotedValue( styleName.isEmpty() ? dsUri.table() : styleName ) )
2036+
.arg( QgsMssqlProvider::quotedValue( qmlStyle ) )
2037+
.arg( QgsMssqlProvider::quotedValue( sldStyle ) )
2038+
.arg( useAsDefault ? "1" : "0" )
2039+
.arg( QgsMssqlProvider::quotedValue( styleDescription.isEmpty() ? QDateTime::currentDateTime().toString() : styleDescription ) )
2040+
.arg( QgsMssqlProvider::quotedValue( dsUri.username() ) )
2041+
.arg( uiFileColumn )
2042+
.arg( uiFileValue );
2043+
2044+
QString checkQuery = QString( "SELECT styleName"
2045+
" FROM layer_styles"
2046+
" WHERE f_table_catalog=%1"
2047+
" AND f_table_schema=%2"
2048+
" AND f_table_name=%3"
2049+
" AND f_geometry_column=%4"
2050+
" AND styleName=%5" )
2051+
.arg( QgsMssqlProvider::quotedValue(dsUri.database() ) )
2052+
.arg( QgsMssqlProvider::quotedValue(dsUri.schema() ) )
2053+
.arg( QgsMssqlProvider::quotedValue(dsUri.table() ) )
2054+
.arg( QgsMssqlProvider::quotedValue(dsUri.geometryColumn() ) )
2055+
.arg( QgsMssqlProvider::quotedValue(styleName.isEmpty() ? dsUri.table() : styleName ) );
2056+
2057+
if ( !query.exec( checkQuery ) )
2058+
{
2059+
QgsDebugMsg( query.lastError().text() );
2060+
QgsDebugMsg("Check Query failed");
2061+
return false;
2062+
}
2063+
if ( query.isActive() && query.next() && query.value( 0 ).toString() == styleName )
2064+
{
2065+
if ( QMessageBox::question( nullptr, QObject::tr( "Save style in database" ),
2066+
QObject::tr( "A style named \"%1\" already exists in the database for this layer. Do you want to overwrite it?" )
2067+
.arg( styleName.isEmpty() ? dsUri.table() : styleName ),
2068+
QMessageBox::Yes | QMessageBox::No ) == QMessageBox::No )
2069+
{
2070+
errCause = QObject::tr( "Operation aborted. No changes were made in the database" );
2071+
QgsDebugMsg("User selected not to overwrite styles");
2072+
return false;
2073+
}
2074+
2075+
QgsDebugMsg("Updating styles");
2076+
sql = QString( "UPDATE layer_styles "
2077+
" SET useAsDefault=%1"
2078+
",styleQML=%2"
2079+
",styleSLD=%3"
2080+
",description=%4"
2081+
",owner=%5"
2082+
" WHERE f_table_catalog=%6"
2083+
" AND f_table_schema=%7"
2084+
" AND f_table_name=%8"
2085+
" AND f_geometry_column=%9"
2086+
" AND styleName=%10" )
2087+
.arg( useAsDefault ? "1" : "0" )
2088+
.arg( QgsMssqlProvider::quotedValue( qmlStyle ) )
2089+
.arg( QgsMssqlProvider::quotedValue( sldStyle ) )
2090+
.arg( QgsMssqlProvider::quotedValue( styleDescription.isEmpty() ? QDateTime::currentDateTime().toString() : styleDescription ) )
2091+
.arg( QgsMssqlProvider::quotedValue( dsUri.username() ) )
2092+
.arg( QgsMssqlProvider::quotedValue( dsUri.database() ) )
2093+
.arg( QgsMssqlProvider::quotedValue( dsUri.schema() ) )
2094+
.arg( QgsMssqlProvider::quotedValue( dsUri.table() ) )
2095+
.arg( QgsMssqlProvider::quotedValue( dsUri.geometryColumn() ) )
2096+
.arg( QgsMssqlProvider::quotedValue( styleName.isEmpty() ? dsUri.table() : styleName ) );
2097+
}
2098+
if ( useAsDefault )
2099+
{
2100+
QString removeDefaultSql = QString( "UPDATE layer_styles "
2101+
" SET useAsDefault=0"
2102+
" WHERE f_table_catalog=%1"
2103+
" AND f_table_schema=%2"
2104+
" AND f_table_name=%3"
2105+
" AND f_geometry_column=%4" )
2106+
.arg( QgsMssqlProvider::quotedValue( dsUri.database() ) )
2107+
.arg( QgsMssqlProvider::quotedValue( dsUri.schema() ) )
2108+
.arg( QgsMssqlProvider::quotedValue( dsUri.table() ) )
2109+
.arg( QgsMssqlProvider::quotedValue( dsUri.geometryColumn() ) );
2110+
sql = QString( "%1; %2;" ).arg( removeDefaultSql, sql );
2111+
}
2112+
2113+
QgsDebugMsg("Inserting styles");
2114+
QgsDebugMsg(sql);
2115+
bool execOk = query.exec( sql );
2116+
2117+
if ( !execOk )
2118+
{
2119+
errCause = QObject::tr( "Unable to save layer style. It's not possible to insert a new record into the style table. Maybe this is due to table permissions. Please contact your database administrator." );
2120+
}
2121+
return execOk;
2122+
}
2123+
2124+
2125+
QGISEXTERN QString loadStyle( const QString& uri, QString& errCause )
2126+
{
2127+
QString style;
2128+
QgsDataSourceURI dsUri( uri );
2129+
// connect to database
2130+
QSqlDatabase mDatabase = QgsMssqlProvider::GetDatabase( dsUri.service(), dsUri.host(), dsUri.database(), dsUri.username(), dsUri.password() );
2131+
2132+
if ( !QgsMssqlProvider::OpenDatabase( mDatabase ) )
2133+
{
2134+
QgsDebugMsg( "Error connecting to database" );
2135+
QgsDebugMsg( mDatabase.lastError().text() );
2136+
return QString();
2137+
}
2138+
2139+
QSqlQuery query = QSqlQuery( mDatabase );
2140+
query.setForwardOnly( true );
2141+
2142+
QString selectQmlQuery = QString( "SELECT top 1 styleQML"
2143+
" FROM layer_styles"
2144+
" WHERE f_table_catalog=%1"
2145+
" AND f_table_schema=%2"
2146+
" AND f_table_name=%3"
2147+
" AND f_geometry_column=%4"
2148+
" ORDER BY useAsDefault desc" )
2149+
.arg( QgsMssqlProvider::quotedValue( dsUri.database() ) )
2150+
.arg( QgsMssqlProvider::quotedValue( dsUri.schema() ) )
2151+
.arg( QgsMssqlProvider::quotedValue( dsUri.table() ) )
2152+
.arg( QgsMssqlProvider::quotedValue( dsUri.geometryColumn() ) );
2153+
2154+
if ( !query.exec( selectQmlQuery ) )
2155+
{
2156+
QgsDebugMsg( query.lastError().text() );
2157+
QgsDebugMsg("Load of Style failed");
2158+
return QString();
2159+
}
2160+
if ( query.isActive() && query.next())
2161+
{
2162+
QString style = query.value( 0 ).toString();
2163+
return style;
2164+
}
2165+
return QString();
2166+
}
2167+
2168+
QGISEXTERN int listStyles( const QString &uri, QStringList &ids, QStringList &names,
2169+
QStringList &descriptions, QString& errCause )
2170+
{
2171+
QgsDataSourceURI dsUri( uri );
2172+
// connect to database
2173+
QSqlDatabase mDatabase = QgsMssqlProvider::GetDatabase( dsUri.service(), dsUri.host(), dsUri.database(), dsUri.username(), dsUri.password() );
2174+
2175+
if ( !QgsMssqlProvider::OpenDatabase( mDatabase ) )
2176+
{
2177+
QgsDebugMsg( "Error connecting to database" );
2178+
QgsDebugMsg( mDatabase.lastError().text() );
2179+
return -1;
2180+
}
2181+
2182+
QSqlQuery query = QSqlQuery( mDatabase );
2183+
query.setForwardOnly( true );
2184+
2185+
// check if layer_styles table already exist
2186+
if ( !query.exec( QString( "SELECT COUNT(*) FROM information_schema.tables WHERE table_name= N'layer_styles'" ) ) )
2187+
{
2188+
QString msg = query.lastError().text();
2189+
errCause = msg;
2190+
QgsDebugMsg( msg );
2191+
return -1;
2192+
}
2193+
if ( query.isActive() && query.next() && query.value( 0 ).toInt() == 0 )
2194+
{
2195+
QgsDebugMsg( QObject::tr( "No styles available on DB, or there is an error connecting to the database." ) );
2196+
return -1;
2197+
}
2198+
2199+
QString selectRelatedQuery = QString( "SELECT id,styleName,description"
2200+
" FROM layer_styles "
2201+
" WHERE f_table_catalog=%1"
2202+
" AND f_table_schema=%2"
2203+
" AND f_table_name=%3"
2204+
" AND f_geometry_column=%4" )
2205+
.arg( QgsMssqlProvider::quotedValue( dsUri.database() ) )
2206+
.arg( QgsMssqlProvider::quotedValue( dsUri.schema() ) )
2207+
.arg( QgsMssqlProvider::quotedValue( dsUri.table() ) )
2208+
.arg( QgsMssqlProvider::quotedValue( dsUri.geometryColumn() ) );
2209+
bool queryOk = query.exec(selectRelatedQuery);
2210+
if(!queryOk)
2211+
{
2212+
QgsDebugMsg( query.lastError().text() );
2213+
return -1;
2214+
}
2215+
int numberOfRelatedStyles = 0;
2216+
while( query.isActive() && query.next())
2217+
{
2218+
QgsDebugMsg(query.value(1).toString());
2219+
ids.append( query.value(0).toString() );
2220+
names.append( query.value(1).toString() );
2221+
descriptions.append( query.value(2).toString() );
2222+
numberOfRelatedStyles = numberOfRelatedStyles + 1;
2223+
}
2224+
QString selectOthersQuery = QString( "SELECT id,styleName,description"
2225+
" FROM layer_styles "
2226+
" WHERE NOT (f_table_catalog=%1 AND f_table_schema=%2 AND f_table_name=%3 AND f_geometry_column=%4)"
2227+
" ORDER BY update_time DESC" )
2228+
.arg( QgsMssqlProvider::quotedValue( dsUri.database() ) )
2229+
.arg( QgsMssqlProvider::quotedValue( dsUri.schema() ) )
2230+
.arg( QgsMssqlProvider::quotedValue( dsUri.table() ) )
2231+
.arg( QgsMssqlProvider::quotedValue( dsUri.geometryColumn() ) );
2232+
QgsDebugMsg(selectOthersQuery);
2233+
queryOk = query.exec(selectOthersQuery);
2234+
if(!queryOk)
2235+
{
2236+
QString msg = query.lastError().text();
2237+
QgsDebugMsg( msg );
2238+
return -1;
2239+
}
2240+
QgsDebugMsg(query.isActive() && query.size());
2241+
while(query.next())
2242+
{
2243+
ids.append( query.value(0).toString() );
2244+
names.append( query.value(1).toString() );
2245+
descriptions.append( query.value(2).toString() );
2246+
}
2247+
return numberOfRelatedStyles;
2248+
}
2249+
QGISEXTERN QString getStyleById( const QString& uri, QString styleId, QString& errCause )
2250+
{
2251+
QgsDataSourceURI dsUri( uri );
2252+
// connect to database
2253+
QSqlDatabase mDatabase = QgsMssqlProvider::GetDatabase( dsUri.service(), dsUri.host(), dsUri.database(), dsUri.username(), dsUri.password() );
2254+
2255+
if ( !QgsMssqlProvider::OpenDatabase( mDatabase ) )
2256+
{
2257+
QgsDebugMsg( "Error connecting to database" );
2258+
QgsDebugMsg( mDatabase.lastError().text() );
2259+
return QString();
2260+
}
2261+
2262+
QSqlQuery query = QSqlQuery( mDatabase );
2263+
query.setForwardOnly( true );
2264+
2265+
QString style = "";
2266+
QString selectQmlQuery = QString( "SELECT styleQml FROM layer_styles WHERE id=%1" ).arg( QgsMssqlProvider::quotedValue( styleId ) );
2267+
bool queryOk = query.exec(selectQmlQuery);
2268+
if(!queryOk)
2269+
{
2270+
QString msg = query.lastError().text();
2271+
QgsDebugMsg( msg );
2272+
errCause = query.lastError().text();
2273+
return QString( );
2274+
}
2275+
while(query.next())
2276+
{
2277+
style = query.value(0).toString();
2278+
}
2279+
return style;
2280+
}

‎src/providers/mssql/qgsmssqlprovider.h‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,11 @@ class QgsMssqlProvider : public QgsVectorDataProvider
186186
*/
187187
bool isValid() override;
188188

189+
/**
190+
* It returns true. Saving style to db is supported by this provider
191+
*/
192+
virtual bool isSaveAndLoadStyleToDBSupported() override { return true; }
193+
189194
/** Writes a list of features to the database*/
190195
virtual bool addFeatures( QgsFeatureList & flist ) override;
191196

@@ -223,6 +228,9 @@ class QgsMssqlProvider : public QgsVectorDataProvider
223228
/** Convert a QgsField to work with MSSQL */
224229
static bool convertField( QgsField &field );
225230

231+
/** Convert values to quoted values for database work **/
232+
static QString quotedValue( const QVariant& value );
233+
226234
/** Returns the default value for field specified by @c fieldId */
227235
QVariant defaultValue( int fieldId ) override;
228236

‎src/providers/postgres/qgspostgresprovider.h‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,6 @@ class QgsPostgresProvider : public QgsVectorDataProvider
175175
*/
176176
bool isValid() override;
177177

178-
179178
/**
180179
* It returns true. Saving style to db is supported by this provider
181180
*/

0 commit comments

Comments
 (0)