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