Line data Source code
1 : /*
2 : * SPDX-License-Identifier: MPL-2.0
3 : *
4 : * This Source Code Form is subject to the terms of the Mozilla Public
5 : * License, v. 2.0. If a copy of the MPL was not distributed with this
6 : * file, You can obtain one at http://mozilla.org/MPL/2.0/.
7 : *
8 : * Copyright 2024 MonetDB Foundation;
9 : * Copyright August 2008 - 2023 MonetDB B.V.;
10 : * Copyright 1997 - July 2008 CWI.
11 : */
12 :
13 : /*
14 : * This code was created by Peter Harvey (mostly during Christmas 98/99).
15 : * This code is LGPL. Please ensure that this message remains in future
16 : * distributions and uses of this code (thats about all I get out of it).
17 : * - Peter Harvey pharvey@codebydesign.com
18 : *
19 : * This file has been modified for the MonetDB project. See the file
20 : * Copyright in this directory for more information.
21 : */
22 :
23 : /**********************************************************************
24 : * SQLStatistics()
25 : * CLI Compliance: ISO 92
26 : *
27 : * Author: Martin van Dinther, Sjoerd Mullender
28 : * Date : 30 aug 2002
29 : *
30 : **********************************************************************/
31 :
32 : #include "ODBCGlobal.h"
33 : #include "ODBCStmt.h"
34 : #include "ODBCUtil.h"
35 :
36 :
37 : #ifdef ODBCDEBUG
38 : static char *
39 0 : translateUnique(SQLUSMALLINT Unique)
40 : {
41 0 : switch (Unique) {
42 : case SQL_INDEX_ALL:
43 : return "SQL_INDEX_ALL";
44 0 : case SQL_INDEX_UNIQUE:
45 0 : return "SQL_INDEX_UNIQUE";
46 0 : default:
47 0 : return "unknown";
48 : }
49 : }
50 :
51 : static char *
52 0 : translateReserved(SQLUSMALLINT Reserved)
53 : {
54 0 : switch (Reserved) {
55 : case SQL_ENSURE:
56 : return "SQL_ENSURE";
57 0 : case SQL_QUICK:
58 0 : return "SQL_QUICK";
59 0 : default:
60 0 : return "unknown";
61 : }
62 : }
63 : #endif
64 :
65 : static SQLRETURN
66 14 : MNDBStatistics(ODBCStmt *stmt,
67 : const SQLCHAR *CatalogName,
68 : SQLSMALLINT NameLength1,
69 : const SQLCHAR *SchemaName,
70 : SQLSMALLINT NameLength2,
71 : const SQLCHAR *TableName,
72 : SQLSMALLINT NameLength3,
73 : SQLUSMALLINT Unique,
74 : SQLUSMALLINT Reserved)
75 : {
76 14 : RETCODE rc;
77 :
78 : /* buffer for the constructed query to do meta data retrieval */
79 14 : char *query = NULL;
80 14 : size_t querylen;
81 14 : size_t pos = 0;
82 14 : char *sch = NULL, *tab = NULL;
83 14 : bool addTmpQuery = false;
84 :
85 14 : fixODBCstring(TableName, NameLength3, SQLSMALLINT,
86 : addStmtError, stmt, return SQL_ERROR);
87 14 : fixODBCstring(SchemaName, NameLength2, SQLSMALLINT,
88 : addStmtError, stmt, return SQL_ERROR);
89 14 : fixODBCstring(CatalogName, NameLength1, SQLSMALLINT,
90 : addStmtError, stmt, return SQL_ERROR);
91 :
92 : #ifdef ODBCDEBUG
93 14 : ODBCLOG("\"%.*s\" \"%.*s\" \"%.*s\" %s %s\n",
94 : (int) NameLength1, CatalogName ? (char *) CatalogName : "",
95 : (int) NameLength2, SchemaName ? (char *) SchemaName : "",
96 : (int) NameLength3, TableName ? (char *) TableName : "",
97 : translateUnique(Unique), translateReserved(Reserved));
98 : #endif
99 :
100 : /* check for valid Unique argument */
101 14 : switch (Unique) {
102 : case SQL_INDEX_ALL:
103 : case SQL_INDEX_UNIQUE:
104 14 : break;
105 0 : default:
106 : /* Uniqueness option type out of range */
107 0 : addStmtError(stmt, "HY100", NULL, 0);
108 0 : return SQL_ERROR;
109 : }
110 :
111 : /* check for valid Reserved argument */
112 14 : switch (Reserved) {
113 : case SQL_ENSURE:
114 : case SQL_QUICK:
115 14 : break;
116 0 : default:
117 : /* Accuracy option type out of range */
118 0 : addStmtError(stmt, "HY101", NULL, 0);
119 0 : return SQL_ERROR;
120 : }
121 :
122 : /* check if a valid (non null, not empty) table name is supplied */
123 14 : if (TableName == NULL) {
124 : /* Invalid use of null pointer */
125 0 : addStmtError(stmt, "HY009", NULL, 0);
126 0 : return SQL_ERROR;
127 : }
128 14 : if (NameLength3 == 0) {
129 : /* Invalid string or buffer length */
130 0 : addStmtError(stmt, "HY090", NULL, 0);
131 0 : return SQL_ERROR;
132 : }
133 :
134 14 : if (stmt->Dbc->sql_attr_metadata_id == SQL_FALSE) {
135 14 : if (NameLength2 > 0) {
136 14 : sch = ODBCParseOA("s", "name",
137 : (const char *) SchemaName,
138 : (size_t) NameLength2);
139 14 : if (sch == NULL)
140 0 : goto nomem;
141 : }
142 14 : if (NameLength3 > 0) {
143 14 : tab = ODBCParseOA("t", "name",
144 : (const char *) TableName,
145 : (size_t) NameLength3);
146 14 : if (tab == NULL)
147 0 : goto nomem;
148 : }
149 : } else {
150 0 : if (NameLength2 > 0) {
151 0 : sch = ODBCParseID("s", "name",
152 : (const char *) SchemaName,
153 : (size_t) NameLength2);
154 0 : if (sch == NULL)
155 0 : goto nomem;
156 : }
157 0 : if (NameLength3 > 0) {
158 0 : tab = ODBCParseID("t", "name",
159 : (const char *) TableName,
160 : (size_t) NameLength3);
161 0 : if (tab == NULL)
162 0 : goto nomem;
163 : }
164 : }
165 :
166 : /* determine if we need to add a query against the tmp.* tables */
167 28 : addTmpQuery = (SchemaName == NULL)
168 14 : || (SchemaName != NULL
169 14 : && (strcmp((const char *) SchemaName, "tmp") == 0
170 6 : || strchr((const char *) SchemaName, '%') != NULL
171 6 : || strchr((const char *) SchemaName, '_') != NULL));
172 :
173 : /* construct the query */
174 14 : querylen = 1200 + (sch ? strlen(sch) : 0) + (tab ? strlen(tab) : 0);
175 14 : if (addTmpQuery)
176 8 : querylen *= 2;
177 14 : query = malloc(querylen);
178 14 : if (query == NULL)
179 0 : goto nomem;
180 :
181 : /* SQLStatistics returns a table with the following columns:
182 : VARCHAR TABLE_CAT
183 : VARCHAR TABLE_SCHEM
184 : VARCHAR TABLE_NAME NOT NULL
185 : SMALLINT NON_UNIQUE
186 : VARCHAR INDEX_QUALIFIER
187 : VARCHAR INDEX_NAME
188 : SMALLINT TYPE NOT NULL
189 : SMALLINT ORDINAL_POSITION
190 : VARCHAR COLUMN_NAME
191 : CHAR(1) ASC_OR_DESC
192 : INTEGER CARDINALITY
193 : INTEGER PAGES
194 : VARCHAR FILTER_CONDITION
195 : */
196 14 : pos += snprintf(query + pos, querylen - pos,
197 : "select cast(null as varchar(1)) as \"TABLE_CAT\", "
198 : "s.name as \"TABLE_SCHEM\", "
199 : "t.name as \"TABLE_NAME\", "
200 : "cast(sys.ifthenelse(k.name is null,1,0) as smallint) as \"NON_UNIQUE\", "
201 : "cast(null as varchar(1)) as \"INDEX_QUALIFIER\", "
202 : "i.name as \"INDEX_NAME\", "
203 : "cast(sys.ifthenelse(i.type = 0, %d, %d) as smallint) as \"TYPE\", "
204 : "cast(kc.nr + 1 as smallint) as \"ORDINAL_POSITION\", "
205 : "c.name as \"COLUMN_NAME\", "
206 : "cast(null as char(1)) as \"ASC_OR_DESC\", "
207 : "cast(sys.ifthenelse(k.name is null,NULL,st.count) as integer) as \"CARDINALITY\", "
208 : "cast(null as integer) as \"PAGES\", "
209 : "cast(null as varchar(1)) as \"FILTER_CONDITION\" "
210 : "from sys.idxs i "
211 : "join sys._tables t on i.table_id = t.id "
212 : "join sys.schemas s on t.schema_id = s.id "
213 : "join sys.objects kc on i.id = kc.id "
214 : "join sys._columns c on (t.id = c.table_id and kc.name = c.name) "
215 : "%sjoin sys.keys k on (k.name = i.name and i.table_id = k.table_id and k.type in (0, 1)) "
216 : "join sys.storage() st on (st.schema = s.name and st.table = t.name and st.column = c.name) "
217 : "where 1=1",
218 : SQL_INDEX_HASHED, SQL_INDEX_OTHER,
219 : (Unique == SQL_INDEX_UNIQUE) ? "" : "left outer ");
220 : /* by using left outer join we also get indices for tables
221 : which have no primary key or unique constraints, so no rows in sys.keys */
222 14 : assert(pos < 1000);
223 :
224 : /* Construct the selection condition query part */
225 14 : if (NameLength1 > 0 && CatalogName != NULL) {
226 : /* filtering requested on catalog name */
227 0 : if (strcmp((char *) CatalogName, stmt->Dbc->dbname) != 0) {
228 : /* catalog name does not match the database name, so return no rows */
229 0 : pos += snprintf(query + pos, querylen - pos, " and 1=2");
230 : }
231 : }
232 14 : if (sch) {
233 : /* filtering requested on schema name */
234 14 : pos += snprintf(query + pos, querylen - pos, " and %s", sch);
235 : }
236 14 : if (tab) {
237 : /* filtering requested on table name */
238 14 : pos += snprintf(query + pos, querylen - pos, " and %s", tab);
239 : }
240 :
241 14 : if (addTmpQuery) {
242 : /* we must also include the indexes of local temporary tables
243 : which are stored in tmp.idxs, tmp._tables, tmp._columns, tmp.objects and tmp.keys */
244 8 : pos += snprintf(query + pos, querylen - pos,
245 : " UNION ALL "
246 : "select cast(null as varchar(1)) as \"TABLE_CAT\", "
247 : "s.name as \"TABLE_SCHEM\", "
248 : "t.name as \"TABLE_NAME\", "
249 : "cast(sys.ifthenelse(k.name is null,1,0) as smallint) as \"NON_UNIQUE\", "
250 : "cast(null as varchar(1)) as \"INDEX_QUALIFIER\", "
251 : "i.name as \"INDEX_NAME\", "
252 : "cast(sys.ifthenelse(i.type = 0, %d, %d) as smallint) as \"TYPE\", "
253 : "cast(kc.nr + 1 as smallint) as \"ORDINAL_POSITION\", "
254 : "c.name as \"COLUMN_NAME\", "
255 : "cast(null as char(1)) as \"ASC_OR_DESC\", "
256 : "cast(sys.ifthenelse(k.name is null,NULL,st.count) as integer) as \"CARDINALITY\", "
257 : "cast(null as integer) as \"PAGES\", "
258 : "cast(null as varchar(1)) as \"FILTER_CONDITION\" "
259 : "from tmp.idxs i "
260 : "join tmp._tables t on i.table_id = t.id "
261 : "join sys.schemas s on t.schema_id = s.id "
262 : "join tmp.objects kc on i.id = kc.id "
263 : "join tmp._columns c on (t.id = c.table_id and kc.name = c.name) "
264 : "%sjoin tmp.keys k on (k.name = i.name and i.table_id = k.table_id and k.type in (0, 1))"
265 : "left outer join sys.storage() st on (st.schema = s.name and st.table = t.name and st.column = c.name) "
266 : "where 1=1",
267 : SQL_INDEX_HASHED, SQL_INDEX_OTHER,
268 : (Unique == SQL_INDEX_UNIQUE) ? "" : "left outer ");
269 :
270 : /* Construct the selection condition query part */
271 8 : if (NameLength1 > 0 && CatalogName != NULL) {
272 : /* filtering requested on catalog name */
273 0 : if (strcmp((char *) CatalogName, stmt->Dbc->dbname) != 0) {
274 : /* catalog name does not match the database name, so return no rows */
275 0 : pos += snprintf(query + pos, querylen - pos, " and 1=2");
276 : }
277 : }
278 8 : if (sch) {
279 : /* filtering requested on schema name */
280 8 : pos += snprintf(query + pos, querylen - pos, " and %s", sch);
281 : }
282 8 : if (tab) {
283 : /* filtering requested on table name */
284 8 : pos += snprintf(query + pos, querylen - pos, " and %s", tab);
285 : }
286 : }
287 14 : assert(pos < (querylen - 74));
288 :
289 14 : if (sch)
290 14 : free(sch);
291 14 : if (tab)
292 14 : free(tab);
293 :
294 : /* add the ordering */
295 14 : pos += strcpy_len(query + pos, " order by \"NON_UNIQUE\", \"TYPE\", \"INDEX_QUALIFIER\", \"INDEX_NAME\", \"ORDINAL_POSITION\"", querylen - pos);
296 14 : assert(pos < querylen);
297 :
298 : /* debug: fprintf(stdout, "SQLStatistics query (pos: %zu, len: %zu):\n%s\n\n", pos, strlen(query), query); */
299 :
300 : /* query the MonetDB data dictionary tables */
301 14 : rc = MNDBExecDirect(stmt, (SQLCHAR *) query, (SQLINTEGER) pos);
302 :
303 14 : free(query);
304 :
305 14 : return rc;
306 :
307 0 : nomem:
308 : /* note that query must be NULL when we get here */
309 0 : if (sch)
310 0 : free(sch);
311 0 : if (tab)
312 0 : free(tab);
313 : /* Memory allocation error */
314 0 : addStmtError(stmt, "HY001", NULL, 0);
315 0 : return SQL_ERROR;
316 : }
317 :
318 : SQLRETURN SQL_API
319 : SQLStatistics(SQLHSTMT StatementHandle,
320 : SQLCHAR *CatalogName,
321 : SQLSMALLINT NameLength1,
322 : SQLCHAR *SchemaName,
323 : SQLSMALLINT NameLength2,
324 : SQLCHAR *TableName,
325 : SQLSMALLINT NameLength3,
326 : SQLUSMALLINT Unique,
327 : SQLUSMALLINT Reserved)
328 : {
329 14 : ODBCStmt *stmt = (ODBCStmt *) StatementHandle;
330 :
331 : #ifdef ODBCDEBUG
332 14 : ODBCLOG("SQLStatistics %p ", StatementHandle);
333 : #endif
334 :
335 14 : if (!isValidStmt(stmt))
336 : return SQL_INVALID_HANDLE;
337 :
338 14 : clearStmtErrors(stmt);
339 :
340 14 : return MNDBStatistics(stmt,
341 : CatalogName, NameLength1,
342 : SchemaName, NameLength2,
343 : TableName, NameLength3,
344 : Unique,
345 : Reserved);
346 : }
347 :
348 : SQLRETURN SQL_API
349 : SQLStatisticsA(SQLHSTMT StatementHandle,
350 : SQLCHAR *CatalogName,
351 : SQLSMALLINT NameLength1,
352 : SQLCHAR *SchemaName,
353 : SQLSMALLINT NameLength2,
354 : SQLCHAR *TableName,
355 : SQLSMALLINT NameLength3,
356 : SQLUSMALLINT Unique,
357 : SQLUSMALLINT Reserved)
358 : {
359 0 : return SQLStatistics(StatementHandle,
360 : CatalogName, NameLength1,
361 : SchemaName, NameLength2,
362 : TableName, NameLength3,
363 : Unique,
364 : Reserved);
365 : }
366 :
367 : SQLRETURN SQL_API
368 : SQLStatisticsW(SQLHSTMT StatementHandle,
369 : SQLWCHAR *CatalogName,
370 : SQLSMALLINT NameLength1,
371 : SQLWCHAR *SchemaName,
372 : SQLSMALLINT NameLength2,
373 : SQLWCHAR *TableName,
374 : SQLSMALLINT NameLength3,
375 : SQLUSMALLINT Unique,
376 : SQLUSMALLINT Reserved)
377 : {
378 0 : ODBCStmt *stmt = (ODBCStmt *) StatementHandle;
379 0 : SQLRETURN rc = SQL_ERROR;
380 0 : SQLCHAR *catalog = NULL, *schema = NULL, *table = NULL;
381 :
382 : #ifdef ODBCDEBUG
383 0 : ODBCLOG("SQLStatisticsW %p ", StatementHandle);
384 : #endif
385 :
386 0 : if (!isValidStmt(stmt))
387 : return SQL_INVALID_HANDLE;
388 :
389 0 : clearStmtErrors(stmt);
390 :
391 0 : fixWcharIn(CatalogName, NameLength1, SQLCHAR, catalog,
392 : addStmtError, stmt, goto bailout);
393 0 : fixWcharIn(SchemaName, NameLength2, SQLCHAR, schema,
394 : addStmtError, stmt, goto bailout);
395 0 : fixWcharIn(TableName, NameLength3, SQLCHAR, table,
396 : addStmtError, stmt, goto bailout);
397 :
398 0 : rc = MNDBStatistics(stmt,
399 : catalog, SQL_NTS,
400 : schema, SQL_NTS,
401 : table, SQL_NTS,
402 : Unique,
403 : Reserved);
404 :
405 0 : bailout:
406 0 : if (catalog)
407 0 : free(catalog);
408 0 : if (schema)
409 0 : free(schema);
410 0 : if (table)
411 0 : free(table);
412 :
413 : return rc;
414 : }
|