Team Ai
Modelpublic

AryaWu/sqlite

sourceHugging Faceupdated 10mo agoView on Hugging Face
0likes
tests_main_sqlite3_create_collation16.c213 linesDownload Raw Back to tests
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}