Skip to content

Commit eb2abb2

Browse files
committed
fileio related changes
1 parent 3e83424 commit eb2abb2

3 files changed

Lines changed: 129 additions & 3 deletions

File tree

apsw/tests/extratest.py

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import subprocess
1111
import sys
1212
import tempfile
13+
import random
1314
import unittest
1415

1516
import apsw
@@ -25,6 +26,7 @@ def testLoadExtension(self):
2526
db = apsw.Connection(":memory:")
2627
for name, extra in self.extras.items():
2728
if extra["type"] == "extension" and apsw.sqlite_extra.has(name):
29+
self.assertEqual("extension", apsw.sqlite_extra.has(name))
2830
with self.subTest(name=name):
2931
if self.verbose:
3032
print(f" >> Extension {name}")
@@ -52,10 +54,10 @@ def testLoadExtension(self):
5254
self.assertGreater(num_diff, 0)
5355

5456
def testExecutable(self):
55-
with tempfile.TemporaryDirectory(prefix="apsw-extra-test") as tmpd:
56-
# https://news.ycombinator.com/item?id=42647101 test case
57+
spicy = "√π⁷≤∞"
5758

58-
spicy = "√π⁷≤∞"
59+
with tempfile.TemporaryDirectory(prefix=f"apsw-extra-{spicy}-test") as tmpd:
60+
# https://news.ycombinator.com/item?id=42647101 test case
5961

6062
with open(pathlib.Path(tmpd) / f"{spicy}.sql", "wt", encoding="utf8") as sqlf:
6163
print(f"CrEaTe Table {spicy}(one, two);\ninsert into {spicy} values(7,8)", file=sqlf)
@@ -78,6 +80,7 @@ def testExecutable(self):
7880

7981
for name, extra in self.extras.items():
8082
if extra["type"] == "executable" and apsw.sqlite_extra.has(name):
83+
self.assertEqual("executable", apsw.sqlite_extra.has(name))
8184
with self.subTest(name=name):
8285
if self.verbose:
8386
print(f" >> Executable {name}")
@@ -187,6 +190,51 @@ def testShell(self):
187190
# deliberate error that shouldn't show sqlite_extra attempt
188191
self.assertRaises(apsw.ExtensionLoadingError, s.process_command, ".load thisdoesnotexistandshouldgiveanerror")
189192

193+
def testOther(self):
194+
self.assertIsNone(apsw.sqlite_extra.has("kjldhsfk does not exist jhdskjfhdsfdsfsd"))
195+
# we don't type check at the moment
196+
self.assertIsNone(apsw.sqlite_extra.has(3 + 4j))
197+
if apsw.sqlite_extra.has("sha1"):
198+
self.assertTrue(os.path.exists(apsw.sqlite_extra.path("sha1")))
199+
200+
def testFileIO(self):
201+
# we add some extra code to make it compile under windows, so
202+
# test that works
203+
if not apsw.sqlite_extra.has("fileio") or not hasattr(apsw, "enable_load_extension"):
204+
return
205+
206+
spicy = "√π⁷≤∞"
207+
with tempfile.TemporaryDirectory(prefix=f"apsw-extra-{spicy}-test") as tmpd:
208+
db = apsw.Connection("")
209+
210+
size = 12345
211+
212+
blob = db.execute("SELECT randomblob(?)", (size,)).get
213+
db.enable_load_extension(True)
214+
db.load_extension(apsw.sqlite_extra.path("fileio"))
215+
216+
# the names came from gemini trying to get contrasting
217+
# utf8 and utf16 encoded lengths
218+
names = (
219+
spicy,
220+
"𐐀𐐁𐐂𐐃𐐄𐐅𐐆𐐇𐐈𐐉𐐊𐐋𐐌𐐍𐐎𐐏𐐐𐐑𐐒𐐓𐐔𐐕𐐖𐐗𐐘",
221+
"The quick brown fox jumps over the lazy dog 1234567",
222+
"😀😁😂🤣😃😄😅😆😉😊😋😎😍😘🥰😗😙😚☺️🙂🤗🤩🤔🤨😐😑😶🙄😏😣😥😮🤐",
223+
)
224+
225+
for name in names:
226+
fname = str(pathlib.Path(tmpd) / f"{name}.blob")
227+
res = db.execute("SELECT writefile(?, ?)", (fname, blob)).get
228+
self.assertEqual(res, size)
229+
230+
self.assertEqual(blob, db.execute("SELECT readfile(?)", (fname,)).get)
231+
232+
233+
# check listing works
234+
for name, data in db.execute("select name, data from fsdir(?)", (tmpd,)):
235+
print(f"{name=}")
236+
self.assertEqual(blob, data)
237+
190238

191239
__all__ = ("Extra",)
192240

src/fileio_win32.c

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
3+
The fileio extension calls two functions that are in the SQLite
4+
library but are not part of the sqlite extension api. For our
5+
extra build we link against libsqlite_tool but that isn't useful.
6+
7+
* APSW is using an amalgamation not that sqlite dll
8+
* APSW doesn't include the qlite_extra_binaries directory in its
9+
runtime library path
10+
* .. and it would result in two SQLites anyway
11+
* .. which would confuse whose malloc is being used and just make
12+
things worse
13+
14+
So we link with this file containing the routines
15+
*/
16+
17+
#if defined(WIN32) || defined(_WIN32)
18+
19+
#include "sqlite3ext.h"
20+
21+
/* this points the api pointers in fileio.c */
22+
SQLITE_EXTENSION_INIT3
23+
24+
#include <windows.h>
25+
26+
/* These implementations are semantically the same as in the SQLite
27+
source but directly use public functions directly */
28+
29+
LPWSTR
30+
sqlite3_win32_utf8_to_unicode(const char *zText)
31+
{
32+
LPWSTR zWideText = 0;
33+
int nChar;
34+
35+
/* the conversion is two passes - first to figure out the
36+
size including null terminator */
37+
nChar = MultiByteToWideChar(CP_UTF8, 0, zText, -1, NULL, 0);
38+
if (!nChar)
39+
return 0;
40+
zWideText = sqlite3_malloc(nChar * sizeof(WCHAR));
41+
if (!zWideText)
42+
return 0;
43+
44+
/* second pass - do the conversion */
45+
nChar = MultiByteToWideChar(CP_UTF8, 0, zText, -1, zWideText, nChar);
46+
if (!nChar)
47+
{
48+
sqlite3_free(zWideText);
49+
return 0;
50+
}
51+
return zWideText;
52+
}
53+
54+
char *
55+
sqlite3_win32_unicode_to_utf8(LPCWSTR zWideText)
56+
{
57+
char *zText = 0;
58+
int nByte;
59+
60+
/* same pattern as above - corresponding opposite API */
61+
nByte = WideCharToMultiByte(CP_UTF8, 0, zWideText, -1, 0, 0, 0, 0);
62+
if (!nByte)
63+
return 0;
64+
zText = sqlite3_malloc(nByte);
65+
if (!zText)
66+
return 0;
67+
68+
nByte = WideCharToMultiByte(CP_UTF8, 0, zWideText, -1, zText, nByte, 0, 0);
69+
if (!nByte)
70+
{
71+
sqlite3_free(zText);
72+
return 0;
73+
}
74+
return zText;
75+
}
76+
77+
#endif

tools/vend.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ def __post_init__(self):
108108
Extra(
109109
name="fileio",
110110
description="Implements SQL functions readfile() and writefile(), and eponymous virtual type 'fsdir'",
111+
sources=["ext/misc/fileio.c", "../src/fileio_win32.c"],
111112
),
112113
# ::TODO:: fossildelta once RBU extension is wrapped
113114
Extra(

0 commit comments

Comments
 (0)