Skip to content

Commit

Permalink
feat: define - eval function
Browse files Browse the repository at this point in the history
  • Loading branch information
nalgeon committed Sep 19, 2022
1 parent 17eac88 commit 75d96ed
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 3 deletions.
71 changes: 70 additions & 1 deletion docs/define.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Write arbitrary functions in SQL (as opposed to [application-defined functions](https://sqlite.org/appfunc.html), which require programming in C, Python, or another language).

Adapted from [statement_vtab.c](https://github.com/0x09/sqlite-statement-vtab/blob/master/statement_vtab.c) by 0x09.
Adapted from [statement_vtab.c](https://github.com/0x09/sqlite-statement-vtab/blob/master/statement_vtab.c) by 0x09 and [eval.c](https://www.sqlite.org/src/file/ext/misc/eval.c) by D. Richard Hipp.

## Scalar functions

Expand Down Expand Up @@ -155,6 +155,53 @@ sqlite> select * from strcut('one;two', ';');
Parse error: no such table: strcut
```

## Arbitrary SQL statements

`eval(SQL[, SEPARATOR])`

Executes arbitrary SQL and returns the result as string (if any):

```sql
select eval('select 42');
42
select eval('select 10 + 32');
42
select eval('select abs(-42)');
42
select eval('select ''hello''');
hello
```

Joins multiple result values via space or custom separator:

```sql
select eval('select 1, 2, 3');
1 2 3
select eval('select 1, 2, 3', '|');
1|2|3
```

Joins multiple result rows into a single string:

```sql
select eval('select 1; select 2; select 3;');
1 2 3
select eval('select 1, 2, 3; select 4, 5, 6; select 7, 8, 9;');
1 2 3 4 5 6 7 8 9
```

Supports DDL and DML statements:

```sql
select eval('create table tmp(value int)');
select eval('insert into tmp(value) values (1), (2), (3)');
select count(*) from tmp;
3
select eval('select value from tmp');
1 2 3
select eval('drop table tmp');
```

## Performance

User-defined functions are compiled into prepared statements, so they are pretty fast even on large datasets.
Expand Down Expand Up @@ -190,6 +237,28 @@ select max(value) from data, plus(data.x);
Run Time: real 0.336 user 0.330145 sys 0.005352
```

## Reference

`define(NAME, BODY)`

Defines a scalar function and stores it in the `sqlean_define` table.

`create virtual table NAME using define((BODY))`

Defines a table-valued function and stores it in the `sqlean_define` table.

`define_free()`

Frees up occupied resources (compiled statements cache). Should always be called before disconnecting.

`eval(SQL[, SEPARATOR])`

Executes arbitrary SQL and returns the result as string (if any).

`undefine(NAME)`

Deletes a previously defined function (scalar or table-valued).

## Usage

```
Expand Down
3 changes: 2 additions & 1 deletion docs/third-party.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ SQLean relies heavily on third-party SQLite extensions and open source libraries
| Library | Author | License |
| ------- | ------ | ------- |
| [crypto-algorithms](https://github.com/B-Con/crypto-algorithms) | Brad Conte | Public Domain |
| [eval.c](https://www.sqlite.org/src/file/ext/misc/eval.c) | D. Richard Hipp | Public Domain |
| extension-functions.c | Liam Healy | Public Domain |
| [fileio.c](https://www.sqlite.org/src/file/ext/misc/fileio.c) | D. Richard Hipp | Public Domain |
| [libstrcmp](https://github.com/Rostepher/libstrcmp) | Ross Bayer | MIT License |
Expand All @@ -13,7 +14,7 @@ SQLean relies heavily on third-party SQLite extensions and open source libraries
| [series.c](https://sqlite.org/src/file/ext/misc/series.c) | D. Richard Hipp | Public Domain |
| [sha1.c](https://sqlite.org/src/file/ext/misc/sha1.c) | D. Richard Hipp | Public Domain |
| sha2.c | [Aaron D. Gifford](https://aarongifford.com/) | 3-Clause BSD License |
| [sqlite3_unicode](https://github.com/Zensey/sqlite3_unicode) | Unknow Author | Public Domain |
| [sqlite3_unicode](https://github.com/Zensey/sqlite3_unicode) | Unknown Author | Public Domain |
| [statement_vtab.c](https://github.com/0x09/sqlite-statement-vtab/blob/master/statement_vtab.c) | 0x09 | Public Domain |
| [uuid.c](https://sqlite.org/src/file/ext/misc/uuid.c) | D. Richard Hipp | Public Domain |
| [vsv.c](http://www.dessus.com/files/vsv.c) | Keith Medcalf | Public Domain |
97 changes: 96 additions & 1 deletion src/sqlite3-define.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// Created by 0x09, Public Domain
// https://github.com/0x09/sqlite-statement-vtab/blob/master/statement_vtab.c

// eval() created by by D. Richard Hipp, Public Domain
// https://www.sqlite.org/src/file/ext/misc/eval.c

// Modified by Anton Zhiyanov, MIT License
// https://github.com/nalgeon/sqlean/

Expand Down Expand Up @@ -72,6 +75,96 @@ static void cache_free() {

#pragma endregion

#pragma region eval

/*
* Structure used to accumulate the output
*/
struct EvalResult {
char* z; /* Accumulated output */
const char* zSep; /* Separator */
int szSep; /* Size of the separator string */
sqlite3_int64 nAlloc; /* Number of bytes allocated for z[] */
sqlite3_int64 nUsed; /* Number of bytes of z[] actually used */
};

/*
* Callback from sqlite_exec() for the eval() function.
*/
static int eval_callback(void* pCtx, int argc, char** argv, char** colnames) {
struct EvalResult* p = (struct EvalResult*)pCtx;
int i;
if (argv == 0) {
return SQLITE_OK;
}
for (i = 0; i < argc; i++) {
const char* z = argv[i] ? argv[i] : "";
size_t sz = strlen(z);
if ((sqlite3_int64)sz + p->nUsed + p->szSep + 1 > p->nAlloc) {
char* zNew;
p->nAlloc = p->nAlloc * 2 + sz + p->szSep + 1;
/* Using sqlite3_realloc64() would be better, but it is a recent
** addition and will cause a segfault if loaded by an older version
** of SQLite. */
zNew = p->nAlloc <= 0x7fffffff ? sqlite3_realloc64(p->z, p->nAlloc) : 0;
if (zNew == 0) {
sqlite3_free(p->z);
memset(p, 0, sizeof(*p));
return SQLITE_NOMEM;
}
p->z = zNew;
}
if (p->nUsed > 0) {
memcpy(&p->z[p->nUsed], p->zSep, p->szSep);
p->nUsed += p->szSep;
}
memcpy(&p->z[p->nUsed], z, sz);
p->nUsed += sz;
}
return SQLITE_OK;
}

/*
* Implementation of the eval(X) and eval(X,Y) SQL functions.
*
* Evaluate the SQL text in X. Return the results, using string
* Y as the separator. If Y is omitted, use a single space character.
*/
static void eval(sqlite3_context* context, int argc, sqlite3_value** argv) {
const char* zSql;
sqlite3* db;
char* zErr = 0;
int rc;
struct EvalResult x;

memset(&x, 0, sizeof(x));
x.zSep = " ";
zSql = (const char*)sqlite3_value_text(argv[0]);
if (zSql == 0) {
return;
}
if (argc > 1) {
x.zSep = (const char*)sqlite3_value_text(argv[1]);
if (x.zSep == 0) {
return;
}
}
x.szSep = (int)strlen(x.zSep);
db = sqlite3_context_db_handle(context);
rc = sqlite3_exec(db, zSql, eval_callback, &x, &zErr);
if (rc != SQLITE_OK) {
sqlite3_result_error(context, zErr, -1);
sqlite3_free(zErr);
} else if (x.zSep == 0) {
sqlite3_result_error_nomem(context);
sqlite3_free(x.z);
} else {
sqlite3_result_text(context, x.z, (int)x.nUsed, sqlite3_free);
}
}

#pragma endregion

#pragma region define scalar function

/*
Expand Down Expand Up @@ -600,10 +693,12 @@ __declspec(dllexport)
#endif
int sqlite3_define_init(sqlite3* db, char** pzErrMsg, const sqlite3_api_routines* pApi) {
SQLITE_EXTENSION_INIT2(pApi);
const int flags = SQLITE_UTF8 | SQLITE_DETERMINISTIC;
const int flags = SQLITE_UTF8 | SQLITE_DIRECTONLY;
sqlite3_create_function(db, "define", 2, flags, NULL, define_compiled, NULL, NULL);
sqlite3_create_function(db, "define_free", 0, flags, NULL, free_compiled, NULL, NULL);
sqlite3_create_function(db, "define_cache", 0, flags, NULL, print_cache, NULL, NULL);
sqlite3_create_function(db, "eval", 1, flags, NULL, eval, NULL, NULL);
sqlite3_create_function(db, "eval", 2, flags, NULL, eval, NULL, NULL);
sqlite3_create_function(db, "undefine", 1, flags, NULL, undefine_function, NULL, NULL);
sqlite3_create_module(db, "define", &define_module, NULL);
return load_functions(db);
Expand Down
17 changes: 17 additions & 0 deletions test/define.sql
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,20 @@ select '54', count(*) = 0 from sqlite_master where type = 'table' and name = 'st
select '55', count(*) = 5 from sqlean_define;

select define_free();

select '61', eval('select 42') = '42';
select '62', eval('select 1, 2, 3') = '1 2 3';
select '63', eval('select 1, 2, 3', ', ') = '1, 2, 3';
select '64', eval('select abs(-42)') = '42';
select '65', eval('select 10 + 32') = '42';
select '66', eval('select ''hello''') = 'hello';
select '67', eval('select null') = '';
select '68', eval('select 1; select 2; select 3;') = '1 2 3';

select '71', eval('create table tmp(value int)') is null;
select '72', count(*) = 1 from sqlite_master where type = 'table' and name = 'tmp';
select '73', eval('insert into tmp(value) values (1), (2), (3)') is null;
select '74', count(*) = 3 from tmp;
select '75', eval('select value from tmp') = '1 2 3';
select '76', eval('drop table tmp') is null;
select '77', count(*) = 0 from sqlite_master where type = 'table' and name = 'tmp';

0 comments on commit 75d96ed

Please sign in to comment.