oxibase 0.3.0

Autonomous relational database management system with MVCC, time-travel queries, and full ACID compliance
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
// Copyright 2025 Stoolap Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Tests for user-defined function persistence
//!
//! These tests verify that user-defined functions are properly persisted
//! to and loaded from the system table across database restarts.

#[cfg(feature = "deno")]
use oxibase::Database;

/// Test basic function persistence and loading
#[cfg(feature = "deno")]
#[test]
fn test_function_persistence_basic() {
    let db = Database::open("memory://test").unwrap();

    // Create a simple function
    db.execute(
        "CREATE FUNCTION test_add(a INTEGER, b INTEGER) RETURNS INTEGER LANGUAGE DENO AS 'return a + b;'",
        (),
    ).expect("Failed to create function");

    // First verify the function works
    let call_result: i64 = db
        .query_one("SELECT test_add(5, 3)", ())
        .expect("Failed to call function");
    assert_eq!(call_result, 8);

    // Verify function is listed in SHOW FUNCTIONS
    let result = db
        .query("SHOW FUNCTIONS", ())
        .expect("Failed to execute SHOW FUNCTIONS");
    let rows: Vec<_> = result.collect();
    assert_eq!(rows.len(), 1);

    // Check the function name
    if let Some(Ok(row)) = rows.first() {
        let name: String = row.get(0).expect("Failed to get name");
        assert_eq!(name, "TEST_ADD");
    }

    // Verify the function can be called
    let result: i64 = db
        .query_one("SELECT test_add(5, 3)", ())
        .expect("Failed to call function");

    assert_eq!(result, 8);
}

#[cfg(feature = "deno")]
#[test]
fn test_function_persistence_restart() {
    let temp_dir = tempfile::tempdir().expect("Failed to create temp dir");
    let db_path = temp_dir
        .path()
        .join("test.db")
        .to_str()
        .unwrap()
        .to_string();

    // First session: create function and persist it
    {
        let db = Database::open(&format!("file://{}", db_path)).expect("Failed to open database");

        // Create a function
        db.execute(
            "CREATE FUNCTION persistent_func(x INTEGER) RETURNS INTEGER LANGUAGE DENO AS 'return x * 2;'",
            (),
        ).expect("Failed to create function");

        // Use the function to verify it works
        let result: i64 = db
            .query_one("SELECT persistent_func(5)", ())
            .expect("Failed to call function");
        assert_eq!(result, 10);
    }

    // Second session: create new database connection and verify function is loaded
    {
        let db = Database::open(&format!("file://{}", db_path)).expect("Failed to reopen database");

        // Function should work after restart (proves it was loaded from system table)
        let result: i64 = db
            .query_one("SELECT persistent_func(7)", ())
            .expect("Failed to call function after restart");
        assert_eq!(result, 14);
    }
}

/// Test multiple functions persistence
#[cfg(feature = "deno")]
#[test]
fn test_multiple_functions_persistence() {
    let db = Database::open("memory://multi_func").expect("Failed to create database");

    // Create multiple functions
    db.execute(
        "CREATE FUNCTION func1(a TEXT) RETURNS TEXT LANGUAGE DENO AS 'return `Hello ${a}`;'",
        (),
    )
    .expect("Failed to create func1");

    db.execute(
        "CREATE FUNCTION func2(x INTEGER, y INTEGER) RETURNS INTEGER LANGUAGE DENO AS 'return Math.max(x, y);'",
        (),
    ).expect("Failed to create func2");

    // Verify both functions are persisted
    let count: i64 = db
        .query_one("SELECT COUNT(*) FROM _sys_functions", ())
        .expect("Failed to count functions");
    assert_eq!(count, 2);

    // Test both functions work
    let result1: String = db
        .query_one("SELECT func1('World')", ())
        .expect("Failed to call func1");
    assert_eq!(result1, "Hello World");

    let result2: i64 = db
        .query_one("SELECT func2(3, 7)", ())
        .expect("Failed to call func2");
    assert_eq!(result2, 7);
}

/// Test CREATE FUNCTION IF NOT EXISTS with persistence
#[cfg(feature = "deno")]
#[test]
fn test_function_if_not_exists_persistence() {
    let db = Database::open("memory://if_not_exists").expect("Failed to create database");

    // Create function first time
    db.execute(
        "CREATE FUNCTION IF NOT EXISTS conditional_func() RETURNS TEXT LANGUAGE DENO AS 'return \"created\";'",
        (),
    ).expect("Failed to create function first time");

    // Try to create again - should not fail
    db.execute(
        "CREATE FUNCTION IF NOT EXISTS conditional_func() RETURNS TEXT LANGUAGE DENO AS 'return \"duplicate\";'",
        (),
    ).expect("Failed to create function second time");

    // Function should still exist and work with original implementation
    let result: String = db
        .query_one("SELECT conditional_func()", ())
        .expect("Failed to call function");
    assert_eq!(result, "created");
}

/// Test that system table starts empty (created on first function)
#[cfg(feature = "deno")]
#[test]
fn test_functions_table_starts_empty() {
    let db = Database::open("memory://empty_table").expect("Failed to create database");

    // System table doesn't exist yet - this should fail
    let result = db.query_one::<i64, _>("SELECT COUNT(*) FROM _sys_functions", ());
    assert!(result.is_err(), "System table should not exist initially");

    // Create a function to trigger table creation
    db.execute(
        "CREATE FUNCTION temp_func() RETURNS INTEGER LANGUAGE DENO AS 'return 42;'",
        (),
    )
    .expect("Failed to create function");

    // Now the table should exist and have 1 row
    let count: i64 = db
        .query_one("SELECT COUNT(*) FROM _sys_functions", ())
        .expect("Failed to count functions");
    assert_eq!(count, 1);
}

#[cfg(feature = "deno")]
#[test]
fn test_show_functions() {
    let db = Database::open("memory://show_functions").expect("Failed to create database");

    // Initially no functions
    let result = db
        .query("SHOW FUNCTIONS", ())
        .expect("Failed to execute SHOW FUNCTIONS");
    let rows: Vec<_> = result.collect();
    assert_eq!(rows.len(), 0);

    // Create some functions
    db.execute(
        "CREATE FUNCTION add_nums(a INTEGER, b INTEGER) RETURNS INTEGER LANGUAGE DENO AS 'return a + b;'",
        (),
    )
    .expect("Failed to create function");

    let result = db
        .query("SELECT name FROM _sys_functions", ())
        .expect("Failed to select functions after first");
    let rows: Vec<_> = result.map(|r| r.unwrap()).collect();
    assert_eq!(rows.len(), 1);
    assert_eq!(rows[0].get::<String>(0).unwrap(), "ADD_NUMS");

    db.execute(
        "CREATE FUNCTION greet(name TEXT) RETURNS TEXT LANGUAGE DENO AS 'return `Hello, ${name}!`;'",
        (),
    )
    .expect("Failed to create function");

    // Check that functions are persisted
    let count: i64 = db
        .query_one("SELECT COUNT(*) FROM _sys_functions", ())
        .expect("Failed to count functions");
    assert_eq!(count, 2);

    // Now SHOW FUNCTIONS should return them
    let result = db
        .query("SHOW FUNCTIONS", ())
        .expect("Failed to execute SHOW FUNCTIONS");
    let rows: Vec<_> = result.map(|r| r.unwrap()).collect();
    assert_eq!(rows.len(), 2);

    // Check first function (ADD_NUMS)
    let row = &rows[0];
    assert_eq!(row.get::<String>(0).unwrap(), "ADD_NUMS");
    assert_eq!(row.get::<String>(1).unwrap(), "(a INTEGER, b INTEGER)");
    assert_eq!(row.get::<String>(2).unwrap(), "INTEGER");
    assert_eq!(row.get::<String>(3).unwrap(), "DENO");
    assert!(row.get::<String>(4).unwrap().contains("a + b"));

    // Check second function (GREET)
    let row = &rows[1];
    assert_eq!(row.get::<String>(0).unwrap(), "GREET");
    assert_eq!(row.get::<String>(1).unwrap(), "(name TEXT)");
    assert_eq!(row.get::<String>(2).unwrap(), "TEXT");
    assert_eq!(row.get::<String>(3).unwrap(), "DENO");
    assert!(row.get::<String>(4).unwrap().contains("Hello"));
}

/// Test basic DROP FUNCTION functionality
#[cfg(feature = "deno")]
#[test]
fn test_drop_function_basic() {
    let db = Database::open("memory://drop_basic").expect("Failed to create database");

    // Create a function
    db.execute(
        "CREATE FUNCTION drop_me(x INTEGER) RETURNS INTEGER LANGUAGE DENO AS 'return x * 3;'",
        (),
    )
    .expect("Failed to create function");

    // Verify function exists and works
    let result: i64 = db
        .query_one("SELECT drop_me(4)", ())
        .expect("Failed to call function");
    assert_eq!(result, 12);

    // Verify it's in system table
    let count: i64 = db
        .query_one(
            "SELECT COUNT(*) FROM _sys_functions WHERE name = 'DROP_ME'",
            (),
        )
        .expect("Failed to count functions");
    assert_eq!(count, 1);

    // Drop the function
    db.execute("DROP FUNCTION drop_me", ())
        .expect("Failed to drop function");

    // Verify it's removed from system table
    let count: i64 = db
        .query_one(
            "SELECT COUNT(*) FROM _sys_functions WHERE name = 'DROP_ME'",
            (),
        )
        .expect("Failed to count functions after drop");
    assert_eq!(count, 0);

    // Verify function can no longer be called
    let result = db.query_one::<i64, _>("SELECT drop_me(4)", ());
    assert!(result.is_err(), "Function should not exist after DROP");
}

/// Test DROP FUNCTION IF EXISTS when function exists
#[cfg(feature = "deno")]
#[test]
fn test_drop_function_if_exists_exists() {
    let db = Database::open("memory://drop_if_exists").expect("Failed to create database");

    // Create a function
    db.execute(
        "CREATE FUNCTION if_exists_func() RETURNS TEXT LANGUAGE DENO AS 'return \"exists\";'",
        (),
    )
    .expect("Failed to create function");

    // Drop with IF EXISTS (should succeed)
    db.execute("DROP FUNCTION IF EXISTS if_exists_func", ())
        .expect("Failed to drop function with IF EXISTS");

    // Verify it's gone
    let count: i64 = db
        .query_one(
            "SELECT COUNT(*) FROM _sys_functions WHERE name = 'IF_EXISTS_FUNC'",
            (),
        )
        .expect("Failed to count functions");
    assert_eq!(count, 0);
}

/// Test DROP FUNCTION IF EXISTS when function doesn't exist
#[cfg(feature = "deno")]
#[test]
fn test_drop_function_if_exists_not_exists() {
    let db = Database::open("memory://drop_if_not_exists").expect("Failed to create database");

    // Drop non-existent function with IF EXISTS (should succeed without error)
    db.execute("DROP FUNCTION IF EXISTS nonexistent_func", ())
        .expect("DROP IF EXISTS should not fail for non-existent function");

    // Verify no functions exist (table might not exist yet)
    let count_result: Result<i64, _> = db.query_one("SELECT COUNT(*) FROM _sys_functions", ());
    match count_result {
        Ok(count) => assert_eq!(count, 0),
        Err(_) => {
            // Table doesn't exist, which is fine - no functions exist
        }
    }
}

/// Test DROP FUNCTION on non-existent function without IF EXISTS
#[cfg(feature = "deno")]
#[test]
fn test_drop_function_not_exists_error() {
    let db = Database::open("memory://drop_error").expect("Failed to create database");

    // Try to drop non-existent function without IF EXISTS (should fail)
    let result = db.execute("DROP FUNCTION nonexistent_func", ());
    assert!(
        result.is_err(),
        "DROP without IF EXISTS should fail for non-existent function"
    );
}

/// Test DROP FUNCTION removes function from registry
#[cfg(feature = "deno")]
#[test]
fn test_drop_function_registry_cleanup() {
    let db = Database::open("memory://drop_registry").expect("Failed to create database");

    // Create multiple functions
    db.execute(
        "CREATE FUNCTION keep_func() RETURNS INTEGER LANGUAGE DENO AS 'return 1;'",
        (),
    )
    .expect("Failed to create keep_func");

    db.execute(
        "CREATE FUNCTION remove_func() RETURNS INTEGER LANGUAGE DENO AS 'return 2;'",
        (),
    )
    .expect("Failed to create remove_func");

    // Verify both work
    let result1: i64 = db
        .query_one("SELECT keep_func()", ())
        .expect("Failed to call keep_func");
    assert_eq!(result1, 1);

    let result2: i64 = db
        .query_one("SELECT remove_func()", ())
        .expect("Failed to call remove_func");
    assert_eq!(result2, 2);

    // Drop one function
    db.execute("DROP FUNCTION remove_func", ())
        .expect("Failed to drop remove_func");

    // Verify keep_func still works
    let result1: i64 = db
        .query_one("SELECT keep_func()", ())
        .expect("keep_func should still work");
    assert_eq!(result1, 1);

    // Verify remove_func is gone from registry (can't be called)
    let result = db.query_one::<i64, _>("SELECT remove_func()", ());
    assert!(
        result.is_err(),
        "remove_func should not be callable after DROP"
    );

    // Verify only one function remains in system table
    let count: i64 = db
        .query_one("SELECT COUNT(*) FROM _sys_functions", ())
        .expect("Failed to count functions");
    assert_eq!(count, 1);

    let name: String = db
        .query_one("SELECT name FROM _sys_functions", ())
        .expect("Failed to get remaining function name");
    assert_eq!(name, "KEEP_FUNC");
}

/// Test DROP FUNCTION persistence across restart
#[cfg(feature = "deno")]
#[test]
fn test_drop_function_persistence_restart() {
    let temp_dir = tempfile::tempdir().expect("Failed to create temp dir");
    let db_path = temp_dir
        .path()
        .join("test_drop.db")
        .to_str()
        .unwrap()
        .to_string();

    // First session: create function, then drop it
    {
        let db = Database::open(&format!("file://{}", db_path)).expect("Failed to open database");

        // Create a function
        db.execute(
            "CREATE FUNCTION temp_drop_func(x INTEGER) RETURNS INTEGER LANGUAGE DENO AS 'return x + 100;'",
            (),
        ).expect("Failed to create function");

        // Verify it works
        let result: i64 = db
            .query_one("SELECT temp_drop_func(5)", ())
            .expect("Failed to call function");
        assert_eq!(result, 105);

        // Drop the function
        db.execute("DROP FUNCTION temp_drop_func", ())
            .expect("Failed to drop function");
    }

    // Second session: verify function stays dropped after restart
    {
        let db = Database::open(&format!("file://{}", db_path)).expect("Failed to reopen database");

        // Function should not exist after restart (proves DROP was persisted)
        let result = db.query_one::<i64, _>("SELECT temp_drop_func(5)", ());
        assert!(
            result.is_err(),
            "Function should remain dropped after restart"
        );

        // System table should be empty or not have the function
        let count: i64 = db
            .query_one(
                "SELECT COUNT(*) FROM _sys_functions WHERE name = 'TEMP_DROP_FUNC'",
                (),
            )
            .expect("Failed to count functions after restart");
        assert_eq!(count, 0);
    }
}

/// Test CREATE FUNCTION with schema-qualified names
#[cfg(feature = "deno")]
#[test]
fn test_create_function_schema_qualified() {
    let db = Database::open("memory://create_schema_func").expect("Failed to create database");

    // Create function with schema-qualified name
    db.execute(
        "CREATE FUNCTION myschema.add_nums(a INTEGER, b INTEGER) RETURNS INTEGER LANGUAGE DENO AS 'return a + b;'",
        (),
    ).expect("Failed to create schema-qualified function");

    // Verify function exists and works
    let result: i64 = db
        .query_one("SELECT myschema.add_nums(3, 4)", ())
        .expect("Failed to call schema-qualified function");
    assert_eq!(result, 7);

    // Verify it's stored with schema
    let schema: Option<String> = db
        .query_one(
            "SELECT schema FROM _sys_functions WHERE name = 'ADD_NUMS'",
            (),
        )
        .expect("Failed to query schema");
    assert_eq!(schema, Some("MYSCHEMA".to_string()));

    let name: String = db
        .query_one(
            "SELECT name FROM _sys_functions WHERE name = 'ADD_NUMS'",
            (),
        )
        .expect("Failed to query name");
    assert_eq!(name, "ADD_NUMS");
}

/// Test DROP FUNCTION with schema-qualified names
#[cfg(feature = "deno")]
#[test]
fn test_drop_function_schema_qualified() {
    let db = Database::open("memory://drop_schema_func").expect("Failed to create database");

    // Create function with schema-qualified name
    db.execute(
        "CREATE FUNCTION test_schema.multiply(x INTEGER, y INTEGER) RETURNS INTEGER LANGUAGE DENO AS 'return x * y;'",
        (),
    ).expect("Failed to create schema-qualified function");

    // Verify it works
    let result: i64 = db
        .query_one("SELECT test_schema.multiply(5, 6)", ())
        .expect("Failed to call function");
    assert_eq!(result, 30);

    // Drop with schema-qualified name
    db.execute("DROP FUNCTION test_schema.multiply", ())
        .expect("Failed to drop schema-qualified function");

    // Verify function is gone
    let result = db.query_one::<i64, _>("SELECT test_schema.multiply(5, 6)", ());
    assert!(result.is_err(), "Function should not exist after DROP");

    // Verify system table is empty
    let count: i64 = db
        .query_one("SELECT COUNT(*) FROM _sys_functions", ())
        .expect("Failed to count functions");
    assert_eq!(count, 0);
}

/// Test function invocation with schema-qualified names
#[cfg(feature = "deno")]
#[test]
fn test_function_call_schema_qualified() {
    let db = Database::open("memory://call_schema_func").expect("Failed to create database");

    // Create function with schema-qualified name
    db.execute(
        "CREATE FUNCTION db.calc_power(base INTEGER, exp INTEGER) RETURNS INTEGER LANGUAGE DENO AS 'return Math.pow(base, exp);'",
        (),
    ).expect("Failed to create function");

    // Call with schema qualification
    let result: f64 = db
        .query_one("SELECT db.calc_power(2, 3)", ())
        .expect("Failed to call with schema qualification");
    assert_eq!(result, 8.0);

    // Call without schema qualification (should still work since functions are global)
    let result2: f64 = db
        .query_one("SELECT calc_power(3, 2)", ())
        .expect("Failed to call without schema qualification");
    assert_eq!(result2, 9.0);
}

/// Test that functions remain global despite schema qualification
#[cfg(feature = "deno")]
#[test]
fn test_functions_remain_global() {
    let db = Database::open("memory://global_funcs").expect("Failed to create database");

    // Create function with different schema qualifications
    db.execute(
        "CREATE FUNCTION schema1.func() RETURNS TEXT LANGUAGE DENO AS 'return \"schema1\";'",
        (),
    )
    .expect("Failed to create function with schema1");

    // Try to create function with same name but different schema (should fail)
    let result = db.execute(
        "CREATE FUNCTION schema2.func() RETURNS TEXT LANGUAGE DENO AS 'return \"schema2\";'",
        (),
    );
    assert!(
        result.is_err(),
        "Should not allow duplicate function names even with different schemas"
    );

    // Function should be accessible with any schema qualification
    let result1: String = db
        .query_one("SELECT schema1.func()", ())
        .expect("Failed to call with original schema");
    assert_eq!(result1, "schema1");

    let result2: String = db
        .query_one("SELECT schema2.func()", ())
        .expect("Failed to call with different schema");
    assert_eq!(result2, "schema1"); // Should return the original function

    let result3: String = db
        .query_one("SELECT func()", ())
        .expect("Failed to call without schema");
    assert_eq!(result3, "schema1"); // Should work without schema
}