AryaWu/sqlite
0
1#include "sqliteInt.h"2#include "unity.h"3#include <string.h>4#include <stdio.h>5 6/* Globals for tests */7static sqlite3 *gDb = NULL;8static int gCmpCalls = 0;9static void *gLastCtx = NULL;10 11/* Helper: build a UTF-16 native-endian string from a UTF-8 ASCII input.12 Allocated with sqlite3_malloc(), must be freed with sqlite3_free(). */13static void *utf16NativeFromUtf8(const char *zIn){14 /* Assume ASCII subset of UTF-8 for test names; 2 bytes per char + 2 bytes NUL */15 size_t n = strlen(zIn);16 sqlite3_uint16 *z16 = (sqlite3_uint16*)sqlite3_malloc((n + 1) * sizeof(sqlite3_uint16));17 if( !z16 ) return NULL;18 for(size_t i = 0; i < n; i++){19 z16[i] = (sqlite3_uint16)((unsigned char)zIn[i]);20 }21 z16[n] = 0;22 return (void*)z16;23}24 25/* Comparator: compare by byte-length (n1 vs n2) */26static int cmp_len(void *pCtx, int n1, const void *p1, int n2, const void *p2){27 (void)p1; (void)p2;28 gCmpCalls++;29 gLastCtx = pCtx;30 if( n1 < n2 ) return -1;31 if( n1 > n2 ) return +1;32 return 0;33}34 35/* Comparator: reverse of cmp_len (longer sorts earlier) */36static int cmp_len_reverse(void *pCtx, int n1, const void *p1, int n2, const void *p2){37 (void)p1; (void)p2;38 gCmpCalls++;39 gLastCtx = pCtx;40 if( n1 < n2 ) return +1;41 if( n1 > n2 ) return -1;42 return 0;43}44 45/* Helper to register a collation by name (UTF-8) using sqlite3_create_collation16.46 Returns the rc from sqlite3_create_collation16. */47static int register_collation16(sqlite3 *db, const char *zNameUtf8, int enc,48 void *pCtx,49 int (*xCompare)(void*,int,const void*,int,const void*)){50 void *z16 = utf16NativeFromUtf8(zNameUtf8);51 TEST_ASSERT_NOT_NULL_MESSAGE(z16, "utf16NativeFromUtf8 returned NULL");52 int rc = sqlite3_create_collation16(db, z16, enc, pCtx, xCompare);53 sqlite3_free(z16);54 return rc;55}56 57/* Helper: execute a scalar SQL that returns an int in column 0 */58static int exec_scalar_int(sqlite3 *db, const char *sql, int *pOut){59 sqlite3_stmt *pStmt = NULL;60 int rc = sqlite3_prepare_v2(db, sql, -1, &pStmt, 0);61 if( rc!=SQLITE_OK ){62 if( pStmt ) sqlite3_finalize(pStmt);63 return rc;64 }65 rc = sqlite3_step(pStmt);66 if( rc==SQLITE_ROW ){67 if( pOut ) *pOut = sqlite3_column_int(pStmt, 0);68 rc = SQLITE_OK;69 }else if( rc==SQLITE_DONE ){70 /* No row; treat as OK with default 0 if requested. */71 if( pOut ) *pOut = 0;72 rc = SQLITE_OK;73 }74 int rc2 = sqlite3_finalize(pStmt);75 if( rc==SQLITE_OK && rc2!=SQLITE_OK ) rc = rc2;76 return rc;77}78 79void setUp(void) {80 int rc = sqlite3_open(":memory:", &gDb);81 TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, rc, "sqlite3_open failed");82 gCmpCalls = 0;83 gLastCtx = NULL;84}85 86void tearDown(void) {87 if( gDb ){88 int rc = sqlite3_close(gDb);89 TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, rc, "sqlite3_close failed");90 gDb = NULL;91 }92}93 94/* Test: basic registration with SQLITE_UTF8 and usage; comparator invoked, pCtx passed */95void test_sqlite3_create_collation16_basic_register_and_use(void){96 int ctx = 12345; /* context to verify is passed to comparator */97 int rc = register_collation16(gDb, "x_len_test", SQLITE_UTF8, &ctx, cmp_len);98 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);99 100 int val = 0;101 rc = exec_scalar_int(gDb,102 "SELECT CASE WHEN 'aa' < 'bbb' COLLATE x_len_test THEN 1 ELSE 0 END;",103 &val104 );105 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);106 TEST_ASSERT_EQUAL_INT(1, val); /* 'aa' (len 2) < 'bbb' (len 3) by length */107 TEST_ASSERT_TRUE(gCmpCalls > 0); /* comparator was called */108 TEST_ASSERT_EQUAL_PTR(&ctx, gLastCtx); /* pCtx propagated */109}110 111/* Test: replacing an existing collation while an active statement exists returns SQLITE_BUSY */112void test_sqlite3_create_collation16_replace_busy(void){113 int rc = register_collation16(gDb, "busycol", SQLITE_UTF8, NULL, cmp_len);114 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);115 116 /* Prepare a statement and step it to keep it active */117 sqlite3_stmt *pStmt = NULL;118 rc = sqlite3_prepare_v2(gDb, "SELECT 'x' COLLATE busycol;", -1, &pStmt, 0);119 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);120 rc = sqlite3_step(pStmt);121 TEST_ASSERT_EQUAL_INT(SQLITE_ROW, rc); /* Now there is an active VDBE */122 123 /* Attempt to replace the existing collation: expect SQLITE_BUSY */124 rc = register_collation16(gDb, "busycol", SQLITE_UTF8, NULL, cmp_len_reverse);125 TEST_ASSERT_EQUAL_INT(SQLITE_BUSY, rc);126 127 /* Finalize statement to release activity */128 rc = sqlite3_finalize(pStmt);129 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);130}131 132/* Test: replacing an existing collation without active statements succeeds and changes behavior */133void test_sqlite3_create_collation16_replace_no_active(void){134 int rc = register_collation16(gDb, "replcol", SQLITE_UTF8, NULL, cmp_len);135 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);136 137 int val = -1;138 rc = exec_scalar_int(gDb,139 "SELECT CASE WHEN 'aa' < 'bbb' COLLATE replcol THEN 1 ELSE 0 END;",140 &val141 );142 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);143 TEST_ASSERT_EQUAL_INT(1, val); /* length compare: 2 < 3 => true */144 145 /* Replace with reverse comparator */146 rc = register_collation16(gDb, "replcol", SQLITE_UTF8, NULL, cmp_len_reverse);147 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);148 149 val = -1;150 rc = exec_scalar_int(gDb,151 "SELECT CASE WHEN 'aa' < 'bbb' COLLATE replcol THEN 1 ELSE 0 END;",152 &val153 );154 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);155 TEST_ASSERT_EQUAL_INT(0, val); /* reverse length compare: 2 < 3 => false */156}157 158/* Test: removing a collation (xCompare=NULL) and verifying it cannot be used */159void test_sqlite3_create_collation16_remove_and_error_on_use(void){160 int rc = register_collation16(gDb, "gonecol", SQLITE_UTF8, NULL, cmp_len);161 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);162 163 /* Remove the collation by passing NULL comparator */164 rc = register_collation16(gDb, "gonecol", SQLITE_UTF8, NULL, NULL);165 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);166 167 /* Now referencing the collation should cause an error */168 sqlite3_stmt *pStmt = NULL;169 rc = sqlite3_prepare_v2(gDb, "SELECT 'a' = 'a' COLLATE gonecol;", -1, &pStmt, 0);170 /* Depending on resolution timing, error may occur at prepare time */171 TEST_ASSERT_NOT_EQUAL_INT(SQLITE_OK, rc);172 173 if( pStmt ){174 sqlite3_finalize(pStmt);175 }176 const char *zErr = sqlite3_errmsg(gDb);177 TEST_ASSERT_NOT_NULL(zErr);178 /* Check the error mentions missing collation (best-effort substring check) */179 TEST_ASSERT_NOT_EQUAL_INT(0, strstr(zErr, "no such collation") == NULL ? 0 : 1);180}181 182/* Test: invalid encoding returns SQLITE_MISUSE */183void test_sqlite3_create_collation16_invalid_encoding(void){184 int rc = register_collation16(gDb, "badenc", 0 /* invalid enc */, NULL, cmp_len);185 TEST_ASSERT_EQUAL_INT(SQLITE_MISUSE, rc);186}187 188/* Test: using SQLITE_UTF16 flag (mapped to native) works */189void test_sqlite3_create_collation16_with_UTF16_flag(void){190 gCmpCalls = 0;191 int rc = register_collation16(gDb, "utf16flag", SQLITE_UTF16, NULL, cmp_len);192 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);193 194 int val = 0;195 rc = exec_scalar_int(gDb,196 "SELECT CASE WHEN 'aa' < 'bbb' COLLATE utf16flag THEN 1 ELSE 0 END;",197 &val198 );199 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);200 TEST_ASSERT_EQUAL_INT(1, val);201 TEST_ASSERT_TRUE(gCmpCalls > 0);202}203 204int main(void){205 UNITY_BEGIN();206 RUN_TEST(test_sqlite3_create_collation16_basic_register_and_use);207 RUN_TEST(test_sqlite3_create_collation16_replace_busy);208 RUN_TEST(test_sqlite3_create_collation16_replace_no_active);209 RUN_TEST(test_sqlite3_create_collation16_remove_and_error_on_use);210 RUN_TEST(test_sqlite3_create_collation16_invalid_encoding);211 RUN_TEST(test_sqlite3_create_collation16_with_UTF16_flag);212 return UNITY_END();213}