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 : #include "monetdb_config.h"
14 : #include "rel_proto_loader.h"
15 : #include "rel_exp.h"
16 :
17 : #include "mal_instruction.h"
18 : #include "mal_interpreter.h"
19 : #include "mal_parser.h"
20 : #include "mal_builder.h"
21 : #include "mal_namespace.h"
22 : #include "mal_exception.h"
23 : #include "mal_linker.h"
24 : #include "mal_backend.h"
25 : #include "sql_types.h"
26 : #include "rel_bin.h"
27 : #include "sql_storage.h"
28 : #include "mapi.h"
29 : #include "msettings.h"
30 :
31 : #include "rel_remote.h"
32 : #include "rel_basetable.h"
33 : #include <unistd.h>
34 :
35 : static char *sql_template(allocator *sa, const char **parts);
36 :
37 : typedef struct mdb_loader_t {
38 : char *uri;
39 : const char *sname;
40 : const char *tname;
41 : } mdb_loader_t;
42 :
43 :
44 : /*
45 : * returns an error string (static or via tmp sa_allocator allocated), NULL on success
46 : *
47 : * Extend the subfunc f with result columns, ie.
48 : f->res = typelist;
49 : f->coltypes = typelist;
50 : f->colnames = nameslist; use tname if passed, for the relation name
51 : * Fill the list res_exps, with one result expressions per resulting column.
52 : */
53 : static str
54 3 : monetdb_relation(mvc *sql, sql_subfunc *f, char *raw_uri, list *res_exps, char *aname)
55 : {
56 3 : str ret; // intentionally uninitialized to provoke control flow warnings
57 :
58 3 : const char *uri_error = NULL;
59 3 : Mapi dbh = NULL;
60 3 : MapiHdl hdl = NULL;
61 :
62 : // Normalize uri
63 3 : msettings *mp = sa_msettings_create(sql->sa);
64 3 : if (!mp) {
65 0 : ret = sa_message(sql->sa, "could not allocate msettings");
66 0 : goto end;
67 : }
68 :
69 6 : if (
70 3 : (uri_error = msettings_parse_url(mp, raw_uri))
71 3 : || (uri_error = msettings_validate(mp))
72 : ) {
73 0 : ret = sa_message(sql->sa, "uri '%s' invalid: %s\n", raw_uri, uri_error);
74 0 : goto end;
75 : }
76 3 : const char *uri = sa_msettings_to_string(mp, sql->sa, strlen(raw_uri));
77 :
78 3 : const char *sname = msetting_string(mp, MP_TABLESCHEMA); // not MP_SCHEMA, that's something else
79 3 : const char *tname = msetting_string(mp, MP_TABLE);
80 3 : assert(sname != NULL && tname != NULL); // msetting_string() never returns NULL, can return ""
81 3 : if (!sname[0] || !tname[0]) {
82 0 : ret = sa_message(sql->sa, "monetdb_loader" "schema and/or table missing in '%s'\n", uri);
83 0 : goto end;
84 : }
85 :
86 : /* set up mapi connection; user and password will possibly be overridden in the uri */
87 3 : dbh = mapi_mapiuri(uri, "monetdb", "monetdb", "sql");
88 3 : if (dbh == NULL) {
89 0 : ret = MAL_MALLOC_FAIL;
90 0 : goto end;
91 : }
92 3 : if (mapi_reconnect(dbh) < 0) {
93 0 : ret = sa_strdup(sql->sa, mapi_error_str(dbh));
94 0 : goto end;
95 : }
96 3 : mapi_cache_limit(dbh, 100);
97 :
98 : /* construct the query with proper quoting */
99 3 : char *query;
100 3 : query = sql_template(sql->sa, (const char*[]) {
101 : "select c.name, c.type, c.type_digits, c.type_scale from sys.schemas s, sys._tables t, sys._columns c where s.name = R'",
102 : sname,
103 : "' and s.id = t.schema_id and t.name = R'",
104 : tname,
105 : "' and t.id = c.table_id order by c.number;",
106 : NULL
107 : });
108 :
109 3 : if ((hdl = mapi_query(dbh, query)) == NULL || mapi_error(dbh)) {
110 0 : const char *err = mapi_error_str(dbh);
111 0 : if (err)
112 0 : ret = sa_strdup(sql->sa, err);
113 : else
114 0 : ret = sa_message(sql->sa, "Failed to access remote server");
115 0 : goto end;
116 : }
117 :
118 3 : if (mapi_get_row_count(hdl) == 0) { /* non existing table */
119 1 : ret = sa_message(sql->sa, "Table %s.%s is missing on remote server", sname, tname);
120 1 : goto end;
121 : }
122 :
123 2 : if (!aname)
124 0 : aname = sa_strdup(sql->sa, tname);
125 :
126 2 : f->tname = sa_strdup(sql->sa, aname);
127 :
128 2 : list *typelist = sa_list(sql->sa);
129 2 : list *nameslist = sa_list(sql->sa);
130 19 : while (mapi_fetch_row(hdl)) {
131 17 : char *nme = sa_strdup(sql->sa, mapi_fetch_field(hdl, 0));
132 17 : char *tpe = mapi_fetch_field(hdl, 1);
133 17 : char *dig = mapi_fetch_field(hdl, 2);
134 17 : char *scl = mapi_fetch_field(hdl, 3);
135 :
136 17 : sql_subtype *t = NULL;
137 34 : if (scl && scl[0]) {
138 17 : int d = (int)strtol(dig, NULL, 10);
139 17 : int s = (int)strtol(scl, NULL, 10);
140 17 : t = sql_bind_subtype(sql->sa, tpe, d, s);
141 0 : } else if (dig && dig[0]) {
142 0 : int d = (int)strtol(dig, NULL, 10);
143 0 : t = sql_bind_subtype(sql->sa, tpe, d, 0);
144 : } else
145 0 : t = sql_bind_subtype(sql->sa, tpe, 0, 0);
146 17 : if (!t) {
147 0 : ret = sa_message(sql->sa, "monetdb_loader" "type %s not found\n", tpe);
148 0 : goto end;
149 : }
150 17 : sql_exp *ne = exp_column(sql->sa, f->tname, nme, t, CARD_MULTI, 1, 0, 0);
151 17 : set_basecol(ne);
152 17 : ne->alias.label = -(sql->nid++);
153 17 : list_append(res_exps, ne);
154 17 : list_append(typelist, t);
155 17 : list_append(nameslist, nme);
156 : }
157 :
158 2 : f->res = typelist;
159 2 : f->coltypes = typelist;
160 2 : f->colnames = nameslist;
161 :
162 2 : mdb_loader_t *r = (mdb_loader_t *)sa_alloc(sql->sa, sizeof(mdb_loader_t));
163 2 : r->sname = sname;
164 2 : r->tname = tname;
165 2 : r->uri = sa_strdup(sql->sa, uri);
166 2 : f->sname = (char*)r; /* pass mdb_loader */
167 2 : ret = NULL;
168 :
169 3 : end:
170 3 : if (hdl)
171 3 : mapi_close_handle(hdl);
172 3 : if (dbh)
173 3 : mapi_destroy(dbh);
174 : // do not destroy mp because r->sname and r->tname point inside it
175 3 : return ret;
176 : }
177 :
178 : /* Return the concatenation of the arguments, with quotes doubled
179 : * ONLY IN THE ODD positions.
180 : * The list of arguments must be terminated with a NULL.
181 : *
182 : * For example, { "SELECT R'", "it's", "' AS bla", NULL }
183 : * becomes "SELECT R'it''s' AS bla"
184 : */
185 : static char *
186 3 : sql_template(allocator *sa, const char **parts)
187 : {
188 3 : int nparts = 0;
189 18 : while (parts[nparts] != NULL)
190 15 : nparts++;
191 :
192 : size_t max_length = 0;
193 18 : for (int i = 0; i < nparts; i++) {
194 15 : size_t length = strlen(parts[i]);
195 15 : if (i % 2 == 1)
196 6 : length += length;
197 15 : max_length += length;
198 : }
199 3 : char *result = sa_alloc(sa, max_length + 1);
200 :
201 : char *w = result;
202 18 : for (int i = 0; i < nparts; i++) {
203 644 : for (const char *r = parts[i]; *r; r++) {
204 629 : *w++ = *r;
205 629 : if (*r == '\'' && i % 2 == 1)
206 0 : *w++ = *r; // double that quote
207 : }
208 : }
209 3 : *w = 0;
210 :
211 3 : assert(w <= result + max_length);
212 3 : return result;
213 : }
214 :
215 :
216 : static void *
217 2 : monetdb_load(void *BE, sql_subfunc *f, char *uri, sql_exp *topn)
218 : {
219 2 : (void)uri; // assumed to be equivalent to mdb_loader_t->uri, though maybe unnormalized.
220 2 : (void)topn;
221 :
222 2 : backend *be = (backend*)BE;
223 2 : mvc *sql = be->mvc;
224 2 : mdb_loader_t *r = (mdb_loader_t*)f->sname;
225 2 : char name[16], *nme;
226 :
227 2 : nme = number2name(name, sizeof(name), ++be->remote);
228 :
229 : /* create proper relation for remote side */
230 : /* table ( project ( REMOTE ( table ) [ cols ] ) [ cols ] REMOTE Prop ) [ cols ] */
231 :
232 2 : sql_table *t;
233 2 : if (mvc_create_table( &t, be->mvc, be->mvc->session->tr->tmp/* misuse tmp schema */, r->tname /*gettable name*/, tt_remote, false, SQL_DECLARED_TABLE, 0, 0, false) != LOG_OK)
234 : /* alloc error */
235 : return NULL;
236 2 : t->query = r->uri; /* set uri */
237 2 : node *n, *nn = f->colnames->h, *tn = f->coltypes->h;
238 19 : for (n = f->res->h; n; n = n->next, nn = nn->next, tn = tn->next) {
239 17 : const char *name = nn->data;
240 17 : sql_subtype *tp = tn->data;
241 17 : sql_column *c = NULL;
242 :
243 17 : if (!tp || mvc_create_column(&c, be->mvc, t, name, tp) != LOG_OK) {
244 0 : return NULL;
245 : }
246 : }
247 :
248 2 : sql_rel *rel = NULL;
249 2 : rel = rel_basetable(sql, t, f->tname);
250 2 : rel_base_use_all(sql, rel);
251 :
252 2 : rel = rel_project(sql->sa, rel, rel_projections(sql, rel, NULL, 0, 0));
253 2 : prop *p = rel->p = prop_create(sql->sa, PROP_REMOTE, rel->p);
254 2 : tid_uri *tu = SA_NEW(sql->sa, tid_uri);
255 2 : tu->id = t->base.id;
256 2 : tu->uri = mapiuri_uri(r->uri, sql->sa);
257 2 : p->id = tu->id;
258 2 : p->value.pval = append(sa_list(sql->sa), tu);
259 :
260 2 : stmt *s = stmt_func(be, NULL, sa_strdup(sql->sa, nme), rel, 0);
261 2 : return s;
262 : }
263 :
264 : static str
265 345 : MONETDBprelude(Client cntxt, MalBlkPtr mb, MalStkPtr stk, InstrPtr pci)
266 : {
267 345 : (void)cntxt; (void)mb; (void)stk; (void)pci;
268 :
269 345 : pl_register("monetdb", &monetdb_relation, &monetdb_load);
270 345 : pl_register("monetdbs", &monetdb_relation, &monetdb_load);
271 345 : pl_register("mapi", &monetdb_relation, &monetdb_load);
272 345 : return MAL_SUCCEED;
273 : }
274 :
275 : static str
276 344 : MONETDBepilogue(void *ret)
277 : {
278 344 : pl_unregister("monetdb");
279 344 : pl_unregister("monetdbs");
280 344 : pl_unregister("mapi");
281 344 : (void)ret;
282 344 : return MAL_SUCCEED;
283 : }
284 :
285 : #include "sql_scenario.h"
286 : #include "mel.h"
287 :
288 : static mel_func monetdb_init_funcs[] = {
289 : pattern("monetdb", "prelude", MONETDBprelude, false, "", noargs),
290 : command("monetdb", "epilogue", MONETDBepilogue, false, "", noargs),
291 : { .imp=NULL }
292 : };
293 :
294 : #include "mal_import.h"
295 : #ifdef _MSC_VER
296 : #undef read
297 : #pragma section(".CRT$XCU",read)
298 : #endif
299 345 : LIB_STARTUP_FUNC(init_monetdb_mal)
300 345 : { mal_module("monetdb", NULL, monetdb_init_funcs); }
|