/src/gdal/apps/gdalalg_vector_sql.cpp
Line | Count | Source |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: GDAL |
4 | | * Purpose: "sql" step of "vector pipeline" |
5 | | * Author: Even Rouault <even dot rouault at spatialys.com> |
6 | | * |
7 | | ****************************************************************************** |
8 | | * Copyright (c) 2025, Even Rouault <even dot rouault at spatialys.com> |
9 | | * |
10 | | * SPDX-License-Identifier: MIT |
11 | | ****************************************************************************/ |
12 | | |
13 | | #include "gdalalg_vector_sql.h" |
14 | | |
15 | | #include "gdal_priv.h" |
16 | | #include "ogrsf_frmts.h" |
17 | | #include "ogrlayerpool.h" |
18 | | |
19 | | #include <mutex> |
20 | | #include <set> |
21 | | |
22 | | //! @cond Doxygen_Suppress |
23 | | |
24 | | #ifndef _ |
25 | 0 | #define _(x) (x) |
26 | | #endif |
27 | | |
28 | | /************************************************************************/ |
29 | | /* GDALVectorSQLAlgorithm::GetConstructorOptions() */ |
30 | | /************************************************************************/ |
31 | | |
32 | | /* static */ GDALVectorSQLAlgorithm::ConstructorOptions |
33 | | GDALVectorSQLAlgorithm::GetConstructorOptions(bool standaloneStep) |
34 | 0 | { |
35 | 0 | ConstructorOptions opts; |
36 | 0 | opts.SetStandaloneStep(standaloneStep); |
37 | 0 | opts.SetOutputDatasetRequired(false); |
38 | 0 | opts.SetAddInputLayerNameArgument(false); |
39 | 0 | opts.SetAddOutputLayerNameArgument(false); |
40 | 0 | opts.SetInputDatasetAlias("dataset"); |
41 | 0 | return opts; |
42 | 0 | } |
43 | | |
44 | | /************************************************************************/ |
45 | | /* GDALVectorSQLAlgorithm::GDALVectorSQLAlgorithm() */ |
46 | | /************************************************************************/ |
47 | | |
48 | | GDALVectorSQLAlgorithm::GDALVectorSQLAlgorithm(bool standaloneStep) |
49 | 0 | : GDALVectorPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL, |
50 | 0 | GetConstructorOptions(standaloneStep)) |
51 | 0 | { |
52 | 0 | auto &sqlArg = AddArg("sql", 0, _("SQL statement(s)"), &m_sql) |
53 | 0 | .SetRequired() |
54 | 0 | .SetPackedValuesAllowed(false) |
55 | 0 | .SetReadFromFileAtSyntaxAllowed() |
56 | 0 | .SetMetaVar("<statement>|@<filename>") |
57 | 0 | .SetRemoveSQLCommentsEnabled(); |
58 | 0 | if (!standaloneStep) |
59 | 0 | sqlArg.SetPositional(); |
60 | 0 | AddArg(GDAL_ARG_NAME_OUTPUT_LAYER, standaloneStep ? 0 : 'l', |
61 | 0 | _("Output layer name(s)"), &m_outputLayer); |
62 | 0 | AddArg("dialect", 0, _("SQL dialect (e.g. OGRSQL, SQLITE)"), &m_dialect); |
63 | 0 | } |
64 | | |
65 | | /************************************************************************/ |
66 | | /* GDALVectorSQLAlgorithmDataset */ |
67 | | /************************************************************************/ |
68 | | |
69 | | namespace |
70 | | { |
71 | | class GDALVectorSQLAlgorithmDataset final : public GDALDataset |
72 | | { |
73 | | GDALDataset &m_oSrcDS; |
74 | | std::vector<OGRLayer *> m_layers{}; |
75 | | |
76 | | CPL_DISALLOW_COPY_ASSIGN(GDALVectorSQLAlgorithmDataset) |
77 | | |
78 | | public: |
79 | | explicit GDALVectorSQLAlgorithmDataset(GDALDataset &oSrcDS) |
80 | 0 | : m_oSrcDS(oSrcDS) |
81 | 0 | { |
82 | 0 | m_oSrcDS.Reference(); |
83 | 0 | } |
84 | | |
85 | | ~GDALVectorSQLAlgorithmDataset() override |
86 | 0 | { |
87 | 0 | for (OGRLayer *poLayer : m_layers) |
88 | 0 | m_oSrcDS.ReleaseResultSet(poLayer); |
89 | 0 | m_oSrcDS.ReleaseRef(); |
90 | 0 | } |
91 | | |
92 | | void AddLayer(OGRLayer *poLayer) |
93 | 0 | { |
94 | 0 | m_layers.push_back(poLayer); |
95 | 0 | } |
96 | | |
97 | | int GetLayerCount() const override |
98 | 0 | { |
99 | 0 | return static_cast<int>(m_layers.size()); |
100 | 0 | } |
101 | | |
102 | | OGRLayer *GetLayer(int idx) const override |
103 | 0 | { |
104 | 0 | return idx >= 0 && idx < GetLayerCount() ? m_layers[idx] : nullptr; |
105 | 0 | } |
106 | | |
107 | | int TestCapability(const char *pszCap) const override |
108 | 0 | { |
109 | 0 | if (EQUAL(pszCap, ODsCCurveGeometries) || |
110 | 0 | EQUAL(pszCap, ODsCMeasuredGeometries) || |
111 | 0 | EQUAL(pszCap, ODsCZGeometries)) |
112 | 0 | { |
113 | 0 | return true; |
114 | 0 | } |
115 | | |
116 | 0 | return false; |
117 | 0 | } |
118 | | }; |
119 | | } // namespace |
120 | | |
121 | | /************************************************************************/ |
122 | | /* GDALVectorSQLAlgorithmDatasetMultiLayer */ |
123 | | /************************************************************************/ |
124 | | |
125 | | namespace |
126 | | { |
127 | | |
128 | | class ProxiedSQLLayer final : public OGRProxiedLayer |
129 | | { |
130 | | mutable OGRFeatureDefnRefCountedPtr m_poLayerDefn{}; |
131 | | mutable std::mutex m_oMutex{}; |
132 | | |
133 | | CPL_DISALLOW_COPY_ASSIGN(ProxiedSQLLayer) |
134 | | |
135 | | public: |
136 | | ProxiedSQLLayer(const std::string &osName, OGRLayerPool *poPoolIn, |
137 | | OpenLayerFunc pfnOpenLayerIn, |
138 | | ReleaseLayerFunc pfnReleaseLayerIn, |
139 | | FreeUserDataFunc pfnFreeUserDataIn, void *pUserDataIn) |
140 | 0 | : OGRProxiedLayer(poPoolIn, pfnOpenLayerIn, pfnReleaseLayerIn, |
141 | 0 | pfnFreeUserDataIn, pUserDataIn) |
142 | 0 | { |
143 | 0 | SetDescription(osName.c_str()); |
144 | 0 | } |
145 | | |
146 | | const char *GetName() const override |
147 | 0 | { |
148 | 0 | return GetDescription(); |
149 | 0 | } |
150 | | |
151 | | const OGRFeatureDefn *GetLayerDefn() const override |
152 | 0 | { |
153 | 0 | std::lock_guard oLock(m_oMutex); |
154 | |
|
155 | 0 | if (!m_poLayerDefn) |
156 | 0 | { |
157 | 0 | m_poLayerDefn.reset(OGRProxiedLayer::GetLayerDefn()->Clone()); |
158 | 0 | m_poLayerDefn->SetName(GetDescription()); |
159 | 0 | } |
160 | 0 | return m_poLayerDefn.get(); |
161 | 0 | } |
162 | | }; |
163 | | |
164 | | class GDALVectorSQLAlgorithmDatasetMultiLayer final : public GDALDataset |
165 | | { |
166 | | // We can't safely have 2 SQL layers active simultaneously on the same |
167 | | // source dataset. So each time we access one, we must close the last |
168 | | // active one. |
169 | | OGRLayerPool m_oPool{1}; |
170 | | GDALDataset &m_oSrcDS; |
171 | | std::vector<std::unique_ptr<ProxiedSQLLayer>> m_layers{}; |
172 | | |
173 | | struct UserData |
174 | | { |
175 | | GDALDataset &oSrcDS; |
176 | | std::string osSQL{}; |
177 | | std::string osDialect{}; |
178 | | std::string osLayerName{}; |
179 | | |
180 | | UserData(GDALDataset &oSrcDSIn, const std::string &osSQLIn, |
181 | | const std::string &osDialectIn, |
182 | | const std::string &osLayerNameIn) |
183 | 0 | : oSrcDS(oSrcDSIn), osSQL(osSQLIn), osDialect(osDialectIn), |
184 | 0 | osLayerName(osLayerNameIn) |
185 | 0 | { |
186 | 0 | } |
187 | | CPL_DISALLOW_COPY_ASSIGN(UserData) |
188 | | }; |
189 | | |
190 | | CPL_DISALLOW_COPY_ASSIGN(GDALVectorSQLAlgorithmDatasetMultiLayer) |
191 | | |
192 | | public: |
193 | | explicit GDALVectorSQLAlgorithmDatasetMultiLayer(GDALDataset &oSrcDS) |
194 | 0 | : m_oSrcDS(oSrcDS) |
195 | 0 | { |
196 | 0 | m_oSrcDS.Reference(); |
197 | 0 | } |
198 | | |
199 | | ~GDALVectorSQLAlgorithmDatasetMultiLayer() override |
200 | 0 | { |
201 | 0 | m_layers.clear(); |
202 | 0 | m_oSrcDS.ReleaseRef(); |
203 | 0 | } |
204 | | |
205 | | void AddLayer(const std::string &osSQL, const std::string &osDialect, |
206 | | const std::string &osLayerName) |
207 | 0 | { |
208 | 0 | const auto OpenLayer = [](void *pUserDataIn) |
209 | 0 | { |
210 | 0 | UserData *pUserData = static_cast<UserData *>(pUserDataIn); |
211 | 0 | return pUserData->oSrcDS.ExecuteSQL( |
212 | 0 | pUserData->osSQL.c_str(), nullptr, |
213 | 0 | pUserData->osDialect.empty() ? nullptr |
214 | 0 | : pUserData->osDialect.c_str()); |
215 | 0 | }; |
216 | |
|
217 | 0 | const auto CloseLayer = [](OGRLayer *poLayer, void *pUserDataIn) |
218 | 0 | { |
219 | 0 | UserData *pUserData = static_cast<UserData *>(pUserDataIn); |
220 | 0 | pUserData->oSrcDS.ReleaseResultSet(poLayer); |
221 | 0 | }; |
222 | |
|
223 | 0 | const auto DeleteUserData = [](void *pUserDataIn) |
224 | 0 | { delete static_cast<UserData *>(pUserDataIn); }; |
225 | |
|
226 | 0 | auto pUserData = new UserData(m_oSrcDS, osSQL, osDialect, osLayerName); |
227 | 0 | m_layers.emplace_back(std::make_unique<ProxiedSQLLayer>( |
228 | 0 | osLayerName, &m_oPool, OpenLayer, CloseLayer, DeleteUserData, |
229 | 0 | pUserData)); |
230 | 0 | } |
231 | | |
232 | | int GetLayerCount() const override |
233 | 0 | { |
234 | 0 | return static_cast<int>(m_layers.size()); |
235 | 0 | } |
236 | | |
237 | | OGRLayer *GetLayer(int idx) const override |
238 | 0 | { |
239 | 0 | return idx >= 0 && idx < GetLayerCount() ? m_layers[idx].get() |
240 | 0 | : nullptr; |
241 | 0 | } |
242 | | }; |
243 | | } // namespace |
244 | | |
245 | | /************************************************************************/ |
246 | | /* GDALVectorSQLAlgorithm::RunStep() */ |
247 | | /************************************************************************/ |
248 | | |
249 | | bool GDALVectorSQLAlgorithm::RunStep(GDALPipelineStepRunContext &) |
250 | 0 | { |
251 | 0 | auto poSrcDS = m_inputDataset[0].GetDatasetRef(); |
252 | 0 | CPLAssert(poSrcDS); |
253 | | |
254 | 0 | auto outputArg = GetArg(GDAL_ARG_NAME_OUTPUT); |
255 | 0 | if (outputArg && !outputArg->IsExplicitlySet()) |
256 | 0 | { |
257 | | // Mode where we update a dataset. |
258 | 0 | for (const auto &sql : m_sql) |
259 | 0 | { |
260 | 0 | const auto nErrorCounter = CPLGetErrorCounter(); |
261 | 0 | OGRLayer *poLayer = poSrcDS->ExecuteSQL( |
262 | 0 | sql.c_str(), nullptr, |
263 | 0 | m_dialect.empty() ? nullptr : m_dialect.c_str()); |
264 | 0 | const bool bResultSet = poLayer != nullptr; |
265 | 0 | poSrcDS->ReleaseResultSet(poLayer); |
266 | 0 | if (bResultSet && !m_quiet) |
267 | 0 | { |
268 | 0 | ReportError(CE_Warning, CPLE_AppDefined, |
269 | 0 | "Execution of the SQL statement '%s' returned a " |
270 | 0 | "result set. It will be ignored. You may silence " |
271 | 0 | "this warning with the 'quiet' argument.", |
272 | 0 | sql.c_str()); |
273 | 0 | } |
274 | 0 | else if (CPLGetErrorCounter() > nErrorCounter && |
275 | 0 | CPLGetLastErrorType() == CE_Failure) |
276 | 0 | { |
277 | 0 | ReportError(CE_Failure, CPLE_AppDefined, |
278 | 0 | "Execution of the SQL statement '%s' failed.%s", |
279 | 0 | sql.c_str(), |
280 | 0 | m_update ? "" |
281 | 0 | : ".\nPerhaps you need to specify the " |
282 | 0 | "'update' argument?"); |
283 | 0 | return false; |
284 | 0 | } |
285 | 0 | } |
286 | 0 | return true; |
287 | 0 | } |
288 | | |
289 | 0 | CPLAssert(m_outputDataset.GetName().empty()); |
290 | 0 | CPLAssert(!m_outputDataset.GetDatasetRef()); |
291 | | |
292 | 0 | if (!m_outputLayer.empty() && m_outputLayer.size() != m_sql.size()) |
293 | 0 | { |
294 | 0 | ReportError(CE_Failure, CPLE_AppDefined, |
295 | 0 | "There should be as many layer names in --output-layer as " |
296 | 0 | "in --statement"); |
297 | 0 | return false; |
298 | 0 | } |
299 | | |
300 | 0 | if (m_sql.size() == 1) |
301 | 0 | { |
302 | 0 | auto outDS = std::make_unique<GDALVectorSQLAlgorithmDataset>(*poSrcDS); |
303 | 0 | outDS->SetDescription(poSrcDS->GetDescription()); |
304 | |
|
305 | 0 | const auto nErrorCounter = CPLGetErrorCounter(); |
306 | 0 | OGRLayer *poLayer = poSrcDS->ExecuteSQL( |
307 | 0 | m_sql[0].c_str(), nullptr, |
308 | 0 | m_dialect.empty() ? nullptr : m_dialect.c_str()); |
309 | 0 | if (!poLayer) |
310 | 0 | { |
311 | 0 | if (nErrorCounter == CPLGetErrorCounter()) |
312 | 0 | { |
313 | 0 | ReportError(CE_Failure, CPLE_AppDefined, |
314 | 0 | "Execution of the SQL statement '%s' did not " |
315 | 0 | "result in a result layer.", |
316 | 0 | m_sql[0].c_str()); |
317 | 0 | } |
318 | 0 | return false; |
319 | 0 | } |
320 | | |
321 | 0 | if (!m_outputLayer.empty()) |
322 | 0 | { |
323 | 0 | const std::string &osLayerName = m_outputLayer[0]; |
324 | 0 | poLayer->GetLayerDefn()->SetName(osLayerName.c_str()); |
325 | 0 | poLayer->SetDescription(osLayerName.c_str()); |
326 | 0 | } |
327 | 0 | outDS->AddLayer(poLayer); |
328 | 0 | m_outputDataset.Set(std::move(outDS)); |
329 | 0 | } |
330 | 0 | else |
331 | 0 | { |
332 | | // First pass to check all statements are valid and figure out layer |
333 | | // names |
334 | 0 | std::set<std::string> setOutputLayerNames; |
335 | 0 | std::vector<std::string> aosLayerNames; |
336 | 0 | for (const std::string &sql : m_sql) |
337 | 0 | { |
338 | 0 | const auto nErrorCounter = CPLGetErrorCounter(); |
339 | 0 | auto poLayer = poSrcDS->ExecuteSQL( |
340 | 0 | sql.c_str(), nullptr, |
341 | 0 | m_dialect.empty() ? nullptr : m_dialect.c_str()); |
342 | 0 | if (!poLayer) |
343 | 0 | { |
344 | 0 | if (nErrorCounter == CPLGetErrorCounter()) |
345 | 0 | { |
346 | 0 | ReportError(CE_Failure, CPLE_AppDefined, |
347 | 0 | "Execution of the SQL statement '%s' did not " |
348 | 0 | "result in a result layer.", |
349 | 0 | sql.c_str()); |
350 | 0 | } |
351 | 0 | return false; |
352 | 0 | } |
353 | | |
354 | 0 | std::string osLayerName; |
355 | |
|
356 | 0 | if (!m_outputLayer.empty()) |
357 | 0 | { |
358 | 0 | osLayerName = m_outputLayer[aosLayerNames.size()]; |
359 | 0 | } |
360 | 0 | else |
361 | 0 | { |
362 | 0 | osLayerName = poLayer->GetDescription(); |
363 | 0 | for (int num = 2; |
364 | 0 | cpl::contains(setOutputLayerNames, osLayerName); ++num) |
365 | 0 | { |
366 | 0 | osLayerName = poLayer->GetDescription(); |
367 | 0 | osLayerName += std::to_string(num); |
368 | 0 | } |
369 | 0 | } |
370 | |
|
371 | 0 | if (!osLayerName.empty()) |
372 | 0 | { |
373 | 0 | poLayer->GetLayerDefn()->SetName(osLayerName.c_str()); |
374 | 0 | poLayer->SetDescription(osLayerName.c_str()); |
375 | 0 | } |
376 | 0 | setOutputLayerNames.insert(poLayer->GetDescription()); |
377 | 0 | aosLayerNames.push_back(poLayer->GetDescription()); |
378 | |
|
379 | 0 | poSrcDS->ReleaseResultSet(poLayer); |
380 | 0 | } |
381 | | |
382 | 0 | auto outDS = |
383 | 0 | std::make_unique<GDALVectorSQLAlgorithmDatasetMultiLayer>(*poSrcDS); |
384 | 0 | outDS->SetDescription(poSrcDS->GetDescription()); |
385 | |
|
386 | 0 | for (size_t i = 0; i < aosLayerNames.size(); ++i) |
387 | 0 | { |
388 | 0 | outDS->AddLayer(m_sql[i], m_dialect, aosLayerNames[i]); |
389 | 0 | } |
390 | |
|
391 | 0 | m_outputDataset.Set(std::move(outDS)); |
392 | 0 | } |
393 | | |
394 | 0 | return true; |
395 | 0 | } |
396 | | |
397 | 0 | GDALVectorSQLAlgorithmStandalone::~GDALVectorSQLAlgorithmStandalone() = default; |
398 | | |
399 | | //! @endcond |