├── .bumpversion.cfg
├── .gitignore
├── .travis.yml
├── Makefile
├── Pipfile
├── Pipfile.lock
├── README.md
├── UNLICENSE
├── src
└── uuid.c
├── test
├── conftest.py
└── test_uuid.py
└── tox.ini
/.bumpversion.cfg:
--------------------------------------------------------------------------------
1 | [bumpversion]
2 | current_version = 1.2.0
3 | commit = True
4 | tag = True
5 |
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | .cache
3 | .pytest_cache/
4 | .tox/
5 | __pycache__
6 | build/
7 | dist/
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | python:
3 | - '3.6'
4 |
5 | matrix:
6 | include:
7 | - os: linux
8 | - os: osx
9 | language: generic
10 | # This is a hack to set up tox-travis on OS X, since this is a generic
11 | # build. Travis sets this automatically for Python builds.
12 | env: TRAVIS_PYTHON_VERSION=3.6
13 |
14 | addons:
15 | apt:
16 | packages:
17 | - gcc
18 | - libssl-dev
19 | - libsqlite3-dev
20 | - uuid-dev
21 |
22 | before_install: |
23 | if [[ $TRAVIS_OS_NAME == 'osx' ]]; then
24 | pyenv install 3.6.5
25 | pyenv global 3.6.5
26 | fi
27 |
28 | install:
29 | - pip install tox-travis
30 |
31 | script:
32 | - make DEBUG=1
33 | - make test USE_DEFAULT_ENTRY_POINT=1
34 |
35 | after_success: |
36 | if [[ $TRAVIS_OS_NAME == 'linux' ]]; then
37 | gcov src/uuid.c
38 | bash <(curl -s https://codecov.io/bash)
39 | fi
40 |
41 | notifications:
42 | email: false
43 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: all clean test
2 |
3 | NAME = uuid
4 | VERSION := $(shell git describe --tags --always --dirty --match v* | sed 's/^v//')
5 | PLATFORM := $(shell uname -s)
6 | CFLAGS = -g -fPIC -Wall -Wextra -Wno-unused-parameter -O2 -lcrypto
7 | LDFLAGS = -shared
8 | DIST_DIR = dist
9 | SOURCES := $(wildcard src/*.c)
10 |
11 | ifeq ($(PLATFORM),Darwin)
12 | SO_EXT = dylib
13 | USE_COMMONCRYPTO = implicit
14 | LIB := $(DIST_DIR)/$(NAME).$(VERSION).$(SO_EXT)
15 | else
16 | SO_EXT = so
17 | USE_OPENSSL = implicit
18 | LDFLAGS += -luuid
19 | LIB := $(DIST_DIR)/$(NAME).$(SO_EXT).$(VERSION)
20 | endif
21 |
22 | ifneq ($(USE_OPENSSL),)
23 | CFLAGS += -DUSE_OPENSSL
24 | endif
25 |
26 | ifneq ($(USE_COMMONCRYPTO),)
27 | CFLAGS += -DUSE_COMMONCRYPTO
28 | endif
29 |
30 | ifneq ($(USE_DEFAULT_ENTRY_POINT),)
31 | CFLAGS += -DUSE_DEFAULT_ENTRY_POINT
32 | endif
33 |
34 | ifneq ($(DEBUG),)
35 | CFLAGS += --coverage
36 | endif
37 |
38 | all: $(LIB)
39 |
40 | clean:
41 | $(RM) -r $(DIST_DIR)
42 | $(RM) *.gcda *.gcno *.gcov
43 |
44 | print-%:
45 | @echo '$*=$($*)'
46 |
47 | test: $(LIB)
48 | tox -- --extension=$(LIB)
49 |
50 | $(LIB): $(SOURCES)
51 | mkdir -p $(DIST_DIR)
52 | $(CC) $< $(CFLAGS) $(LDFLAGS) -o $@
53 |
--------------------------------------------------------------------------------
/Pipfile:
--------------------------------------------------------------------------------
1 | [[source]]
2 | url = "https://pypi.org/simple"
3 | verify_ssl = true
4 | name = "pypi"
5 |
6 | [packages]
7 |
8 | [dev-packages]
9 | tox = "*"
10 |
11 | [requires]
12 | python_version = "3.6"
13 |
--------------------------------------------------------------------------------
/Pipfile.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_meta": {
3 | "hash": {
4 | "sha256": "5d37ba76d9b94b13859453afd23913177971174579be6d6a7410dce320a404ed"
5 | },
6 | "pipfile-spec": 6,
7 | "requires": {
8 | "python_version": "3.6"
9 | },
10 | "sources": [
11 | {
12 | "name": "pypi",
13 | "url": "https://pypi.org/simple",
14 | "verify_ssl": true
15 | }
16 | ]
17 | },
18 | "default": {},
19 | "develop": {
20 | "pluggy": {
21 | "hashes": [
22 | "sha256:6e3836e39f4d36ae72840833db137f7b7d35105079aee6ec4a62d9f80d594dd1",
23 | "sha256:95eb8364a4708392bae89035f45341871286a333f749c3141c20573d2b3876e1"
24 | ],
25 | "version": "==0.7.1"
26 | },
27 | "py": {
28 | "hashes": [
29 | "sha256:3fd59af7435864e1a243790d322d763925431213b6b8529c6ca71081ace3bbf7",
30 | "sha256:e31fb2767eb657cbde86c454f02e99cb846d3cd9d61b318525140214fdc0e98e"
31 | ],
32 | "version": "==1.5.4"
33 | },
34 | "six": {
35 | "hashes": [
36 | "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
37 | "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
38 | ],
39 | "version": "==1.11.0"
40 | },
41 | "tox": {
42 | "hashes": [
43 | "sha256:37cf240781b662fb790710c6998527e65ca6851eace84d1595ee71f7af4e85f7",
44 | "sha256:eb61aa5bcce65325538686f09848f04ef679b5cd9b83cc491272099b28739600"
45 | ],
46 | "index": "pypi",
47 | "version": "==3.2.1"
48 | },
49 | "virtualenv": {
50 | "hashes": [
51 | "sha256:2ce32cd126117ce2c539f0134eb89de91a8413a29baac49cbab3eb50e2026669",
52 | "sha256:ca07b4c0b54e14a91af9f34d0919790b016923d157afda5efdde55c96718f752"
53 | ],
54 | "version": "==16.0.0"
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # sqlite3-uuid
2 |
3 | [](https://travis-ci.org/benwebber/sqlite3-uuid)
4 | [](https://codecov.io/gh/benwebber/sqlite3-uuid)
5 |
6 | This SQLite extension implements functions for creating [RFC 4122](https://www.ietf.org/rfc/rfc4122.txt) compliant UUIDs.
7 |
8 | ## Installation
9 |
10 | 1. Install the necessary development headers. Mac OS X ships with SQLite and the development headers, but you may need to install them on Linux.
11 |
12 | * Debian / Ubuntu:
13 |
14 | ```
15 | sudo apt-get install libsqlite3-dev libssl-dev uuid-dev
16 | ```
17 |
18 | * Red Hat / CentOS:
19 |
20 | ```
21 | sudo yum install openssl-devel sqlite-devel uuid-devel
22 | ```
23 |
24 | 2. Build the extension. The build will produce a shared library for your platform under `dist/`.
25 |
26 | ```
27 | make
28 | ```
29 |
30 | 2. Load the extension using your SQLite API of choice. For example, in Python,
31 |
32 | ```python
33 | import sqlite3
34 |
35 | con = sqlite3.connect(':memory:')
36 |
37 | con.enable_load_extension(True)
38 | con.load_extension('/path/to/uuid')
39 | con.enable_load_extension(False)
40 |
41 | for row in con.execute('SELECT uuid4();'):
42 | print(row)
43 | ```
44 |
45 | ## Usage
46 |
47 | ### `uuid1()`
48 |
49 | Generate a version 1 UUID, based on the time and host machine's MAC address.
50 |
51 | ```sql
52 | SELECT uuid1();
53 | d5a80b20-0d8f-11e5-b8cb-080027b6ec40
54 | ```
55 |
56 | ### `uuid3()`
57 |
58 | Generate a version 3 (MD5) namespace UUID.
59 |
60 | ```
61 | SELECT uuid3(uuid_ns_dns(), 'example.org');
62 | 04738bdf-b25a-3829-a801-b21a1d25095b
63 | ```
64 |
65 | ### `uuid4()`
66 |
67 | Generate a version 4 (random) UUID.
68 |
69 | ```sql
70 | SELECT uuid4();
71 | 1e607604-f360-4fa5-863a-bc91adc70bb9
72 | ```
73 |
74 | ### `uuid5()`
75 |
76 | Generate a version 5 (SHA1) namespace UUID.
77 |
78 | ```
79 | SELECT uuid3(uuid_ns_dns(), 'example.org');
80 | aad03681-8b63-5304-89e0-8ca8f49461b5
81 | ```
82 |
83 | ### `uuid_nil()`
84 |
85 | Generate the nil UUID.
86 |
87 | ```
88 | SELECT uuid_nil();
89 | 00000000-0000-0000-0000-000000000000
90 | ```
91 |
92 | ### Namespaces
93 |
94 | #### DNS
95 |
96 | ```
97 | SELECT uuid_ns_dns();
98 | 6ba7b810-9dad-11d1-80b4-00c04fd430c8
99 | ```
100 |
101 | #### OID
102 |
103 | ```
104 | SELECT uuid_ns_oid();
105 | 6ba7b812-9dad-11d1-80b4-00c04fd430c8
106 | ```
107 |
108 | #### URL
109 |
110 | ```
111 | SELECT uuid_ns_url();
112 | 6ba7b811-9dad-11d1-80b4-00c04fd430c8
113 | ```
114 |
115 | #### X500
116 |
117 | ```
118 | SELECT uuid_ns_x500();
119 | 6ba7b814-9dad-11d1-80b4-00c04fd430c8
120 | ```
121 |
122 | ### Converting between types
123 |
124 | #### `uuid_to_blob()`
125 |
126 | Convert a TEXT UUID to a 16-byte BLOB.
127 |
128 | ```
129 | sqlite3 -cmd '.load uuid' uuid.db 'SELECT uuid_to_blob("3dfe5c62-e543-46ff-a2e0-0b1017506be0");' |
130 | tr -d '\n' |
131 | hexdump -C
132 | 00000000 3d fe 5c 62 e5 43 46 ff a2 e0 0b 10 17 50 6b e0 |=.\b.CF......Pk.|
133 | 00000010
134 | ```
135 |
136 | ### `uuid_to_text()`
137 |
138 | Convert a 16-byte BLOB to a TEXT UUID.
139 |
140 | ```
141 | SELECT uuid_to_text(uuid_to_blob('3dfe5c62-e543-46ff-a2e0-0b1017506be0'));
142 | 3dfe5c62-e543-46ff-a2e0-0b1017506be0
143 | ```
144 |
145 | ## Notes
146 |
147 | This extension only supports Linux and Mac OS X at the moment.
148 |
--------------------------------------------------------------------------------
/UNLICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
25 |
--------------------------------------------------------------------------------
/src/uuid.c:
--------------------------------------------------------------------------------
1 | /*
2 | ** The author disclaims copyright to this source code. In place of
3 | ** a legal notice, here is a blessing:
4 | **
5 | ** May you do good and not evil.
6 | ** May you find forgiveness for yourself and forgive others.
7 | ** May you share freely, never taking more than you give.
8 | **
9 | *************************************************************************
10 | **
11 | ** This SQLite extension implements functions for creating RFC 4122 compliant
12 | ** UUIDs.
13 | **
14 | */
15 | #include
16 | SQLITE_EXTENSION_INIT1
17 | #include
18 | #include
19 | #include
20 |
21 | #ifdef USE_OPENSSL
22 | #include
23 | #include
24 | #endif
25 |
26 | #ifdef USE_COMMONCRYPTO
27 | #define COMMON_DIGEST_FOR_OPENSSL
28 | #include
29 | #endif
30 |
31 | #ifdef USE_DEFAULT_ENTRY_POINT
32 | #define ENTRY_POINT sqlite3_extension_init
33 | #else
34 | #define ENTRY_POINT sqlite3_uuid_init
35 | #endif
36 |
37 | #define SET_VARIANT(uu) (uu[8] = (uu[8] & 0xbf) | 0x80)
38 | #define SET_VERSION(uu, version) (uu[6] = (uu[6] & 0x0f) | (version << 4))
39 |
40 | #define UUID_LENGTH 36
41 |
42 | typedef char _uuid_string_t[37];
43 |
44 | static const _uuid_string_t NAMESPACE_DNS = "6ba7b810-9dad-11d1-80b4-00c04fd430c8";
45 | static const _uuid_string_t NAMESPACE_OID = "6ba7b812-9dad-11d1-80b4-00c04fd430c8";
46 | static const _uuid_string_t NAMESPACE_URL = "6ba7b811-9dad-11d1-80b4-00c04fd430c8";
47 | static const _uuid_string_t NAMESPACE_X500 = "6ba7b814-9dad-11d1-80b4-00c04fd430c8";
48 | static const _uuid_string_t NIL_UUID = "00000000-0000-0000-0000-000000000000";
49 |
50 | /*
51 | ** Implementation of uuid1() function.
52 | */
53 | static void uuid1(
54 | sqlite3_context *context,
55 | int argc,
56 | sqlite3_value **argv
57 | ){
58 | assert(argc==0);
59 | uuid_t uuid;
60 | uuid_generate_time(uuid);
61 | _uuid_string_t uuid_str;
62 | uuid_unparse_lower(uuid, uuid_str);
63 | sqlite3_result_text(context, uuid_str, UUID_LENGTH, SQLITE_TRANSIENT);
64 | }
65 |
66 | /*
67 | ** Implementation of the uuid3() function.
68 | */
69 | static void uuid3(
70 | sqlite3_context *context,
71 | int argc,
72 | sqlite3_value **argv
73 | ){
74 | assert(argc==2);
75 | uuid_t namespace_uuid;
76 | _uuid_string_t uuid_str;
77 |
78 | const unsigned char *namespace = sqlite3_value_text(argv[0]);
79 | const unsigned char *name = sqlite3_value_text(argv[1]);
80 |
81 | if(uuid_parse((const char *)namespace, namespace_uuid) == -1) {
82 | sqlite3_result_error(context, "cannot parse namespace UUID", -1);
83 | return;
84 | }
85 |
86 | uuid_t uu;
87 |
88 | MD5_CTX mdctx;
89 | unsigned char md_value[MD5_DIGEST_LENGTH];
90 | MD5_Init(&mdctx);
91 | MD5_Update(&mdctx, namespace_uuid, sizeof(uuid_t));
92 | MD5_Update(&mdctx, name, strlen((const char *)name));
93 | MD5_Final(md_value, &mdctx);
94 |
95 | SET_VARIANT(md_value);
96 | SET_VERSION(md_value, 3);
97 |
98 | memcpy(uu, md_value, sizeof(uuid_t));
99 |
100 | uuid_unparse_lower(uu, uuid_str);
101 | sqlite3_result_text(context, uuid_str, UUID_LENGTH, SQLITE_TRANSIENT);
102 | }
103 |
104 | /*
105 | ** Implementation of the uuid4() function.
106 | */
107 | static void uuid4(
108 | sqlite3_context *context,
109 | int argc,
110 | sqlite3_value **argv
111 | ){
112 | assert(argc==0);
113 | uuid_t uuid;
114 | uuid_generate_random(uuid);
115 | _uuid_string_t uuid_str;
116 | uuid_unparse_lower(uuid, uuid_str);
117 | sqlite3_result_text(context, uuid_str, UUID_LENGTH, SQLITE_TRANSIENT);
118 | }
119 |
120 | /*
121 | ** Implementation of the uuid5() function.
122 | */
123 | static void uuid5(
124 | sqlite3_context *context,
125 | int argc,
126 | sqlite3_value **argv
127 | ){
128 | assert(argc==2);
129 | uuid_t namespace_uuid;
130 | _uuid_string_t uuid_str;
131 |
132 | const unsigned char *namespace = sqlite3_value_text(argv[0]);
133 | const unsigned char *name = sqlite3_value_text(argv[1]);
134 |
135 | if(uuid_parse((const char *)namespace, namespace_uuid) == -1) {
136 | sqlite3_result_error(context, "cannot parse namespace UUID", -1);
137 | return;
138 | }
139 |
140 | uuid_t uu;
141 |
142 | SHA_CTX mdctx;
143 | unsigned char md_value[SHA_DIGEST_LENGTH];
144 | SHA1_Init(&mdctx);
145 | SHA1_Update(&mdctx, namespace_uuid, sizeof(uuid_t));
146 | SHA1_Update(&mdctx, name, strlen((const char *)name));
147 | SHA1_Final(md_value, &mdctx);
148 |
149 | SET_VARIANT(md_value);
150 | SET_VERSION(md_value, 5);
151 |
152 | memcpy(uu, md_value, sizeof(uuid_t));
153 |
154 | uuid_unparse_lower(uu, uuid_str);
155 | sqlite3_result_text(context, uuid_str, UUID_LENGTH, SQLITE_TRANSIENT);
156 | }
157 |
158 | static void uuid_nil(
159 | sqlite3_context *context,
160 | int argc,
161 | sqlite3_value **argv
162 | ){
163 | assert(argc==0);
164 | sqlite3_result_text(context, NIL_UUID, UUID_LENGTH, SQLITE_STATIC);
165 | }
166 |
167 | static void uuid_ns_dns(
168 | sqlite3_context *context,
169 | int argc,
170 | sqlite3_value **argv
171 | ){
172 | assert(argc==0);
173 | sqlite3_result_text(context, NAMESPACE_DNS, UUID_LENGTH, SQLITE_STATIC);
174 | }
175 |
176 | static void uuid_ns_oid(
177 | sqlite3_context *context,
178 | int argc,
179 | sqlite3_value **argv
180 | ){
181 | assert(argc==0);
182 | sqlite3_result_text(context, NAMESPACE_OID, UUID_LENGTH, SQLITE_STATIC);
183 | }
184 |
185 | static void uuid_ns_url(
186 | sqlite3_context *context,
187 | int argc,
188 | sqlite3_value **argv
189 | ){
190 | assert(argc==0);
191 | sqlite3_result_text(context, NAMESPACE_URL, UUID_LENGTH, SQLITE_STATIC);
192 | }
193 |
194 | static void uuid_ns_x500(
195 | sqlite3_context *context,
196 | int argc,
197 | sqlite3_value **argv
198 | ){
199 | assert(argc==0);
200 | sqlite3_result_text(context, NAMESPACE_X500, UUID_LENGTH, SQLITE_STATIC);
201 | }
202 |
203 | static void uuid_to_text(
204 | sqlite3_context *context,
205 | int argc,
206 | sqlite3_value **argv
207 | ){
208 | assert(argc==1);
209 | const void *uuid_bytes = sqlite3_value_blob(argv[0]);
210 | if(sqlite3_value_bytes(argv[0]) != sizeof(uuid_t)) {
211 | sqlite3_result_error(context, "UUID must be 16 bytes", -1);
212 | return;
213 | }
214 | _uuid_string_t uuid_str;
215 | uuid_unparse_lower(*(uuid_t *)uuid_bytes, uuid_str);
216 | sqlite3_result_text(context, uuid_str, UUID_LENGTH, SQLITE_TRANSIENT);
217 | }
218 |
219 | static void uuid_to_blob(
220 | sqlite3_context *context,
221 | int argc,
222 | sqlite3_value **argv
223 | ){
224 | assert(argc==1);
225 | uuid_t uuid;
226 | const unsigned char *uuid_str = sqlite3_value_text(argv[0]);
227 | if(uuid_parse((const char *)uuid_str, uuid) == -1) {
228 | sqlite3_result_error(context, "cannot parse UUID", -1);
229 | return;
230 | }
231 | sqlite3_result_blob(context, uuid, sizeof(uuid_t), SQLITE_TRANSIENT);
232 | }
233 |
234 | /*
235 | ** Register UUID functions to database `db`.
236 | */
237 | static int register_uuid_functions(sqlite3 *db) {
238 | typedef struct UUIDScalar {
239 | const char *name;
240 | int argc;
241 | int enc;
242 | void (*func)(sqlite3_context*, int, sqlite3_value**);
243 | } UUIDScalar;
244 |
245 | UUIDScalar scalars[] = {
246 | {"uuid1", 0, SQLITE_UTF8, uuid1},
247 | {"uuid3", 2, SQLITE_UTF8, uuid3},
248 | {"uuid4", 0, SQLITE_UTF8, uuid4},
249 | {"uuid5", 2, SQLITE_UTF8, uuid5},
250 | {"uuid_nil", 0, SQLITE_UTF8, uuid_nil},
251 | {"uuid_ns_dns", 0, SQLITE_UTF8, uuid_ns_dns},
252 | {"uuid_ns_oid", 0, SQLITE_UTF8, uuid_ns_oid},
253 | {"uuid_ns_url", 0, SQLITE_UTF8, uuid_ns_url},
254 | {"uuid_ns_x500", 0, SQLITE_UTF8, uuid_ns_x500},
255 | {"uuid_to_text", 1, SQLITE_UTF8, uuid_to_text},
256 | {"uuid_to_blob", 1, SQLITE_UTF8, uuid_to_blob},
257 | };
258 |
259 | int rc = SQLITE_OK;
260 | int i, n;
261 |
262 | n = (int)(sizeof(scalars)/sizeof(scalars[0]));
263 |
264 | for (i = 0; rc == SQLITE_OK && i < n; i++) {
265 | UUIDScalar *s = &scalars[i];
266 | rc = sqlite3_create_function(db, s->name, s->argc, s->enc, 0, s->func, 0, 0);
267 | }
268 |
269 | return rc;
270 | }
271 |
272 | #ifdef _WIN32
273 | __declspec(dllexport)
274 | #endif
275 | int ENTRY_POINT(
276 | sqlite3 *db,
277 | char **pzErrMsg,
278 | const sqlite3_api_routines *pApi
279 | ){
280 | SQLITE_EXTENSION_INIT2(pApi);
281 | return register_uuid_functions(db);
282 | }
283 |
--------------------------------------------------------------------------------
/test/conftest.py:
--------------------------------------------------------------------------------
1 | import collections
2 | import os.path
3 | import uuid
4 | from typing import NamedTuple
5 |
6 | import apsw
7 | import pytest
8 |
9 |
10 | class NamespaceExample(NamedTuple):
11 | namespace: uuid.UUID
12 | name: str
13 |
14 |
15 | def pytest_addoption(parser):
16 | parser.addoption(
17 | '--extension',
18 | action='store',
19 | help='path to sqlite3-uuid shared library',
20 | default=os.path.join(os.path.dirname(__file__), '../dist/uuid'),
21 | )
22 |
23 |
24 | @pytest.fixture
25 | def extension(request):
26 | return request.config.getoption('--extension')
27 |
28 |
29 | @pytest.fixture
30 | def db(extension):
31 | conn = apsw.Connection(':memory:')
32 | conn.enableloadextension(True)
33 | conn.loadextension(extension)
34 | conn.enableloadextension(False)
35 | return conn
36 |
37 |
38 | @pytest.fixture
39 | def examples():
40 | return [
41 | NamespaceExample(uuid.NAMESPACE_DNS, 'example.org'),
42 | NamespaceExample(uuid.NAMESPACE_DNS, 'www.example.org'),
43 | NamespaceExample(uuid.NAMESPACE_OID, '0.1.2.3'),
44 | NamespaceExample(uuid.NAMESPACE_OID, '0.1.2.3.4'),
45 | NamespaceExample(uuid.NAMESPACE_URL, 'https://example.org'),
46 | NamespaceExample(uuid.NAMESPACE_URL, 'https://www.example.org'),
47 | NamespaceExample(uuid.NAMESPACE_X500, 'cn=www.example.org,ou=Technology,o=Internet Corporation for Assigned Names and Numbers,L=Los Angeles,ST=California,C=US'),
48 | ]
49 |
50 |
51 | @pytest.fixture
52 | def uuid3_examples(examples):
53 | return [((str(e.namespace), e.name), uuid.uuid3(e.namespace, e.name))
54 | for e in examples]
55 |
56 |
57 | @pytest.fixture
58 | def uuid5_examples(examples):
59 | return [((str(e.namespace), e.name), uuid.uuid5(e.namespace, e.name))
60 | for e in examples]
61 |
--------------------------------------------------------------------------------
/test/test_uuid.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | """
4 | sqlite-uuid test suite
5 | """
6 |
7 | import secrets
8 | import uuid
9 |
10 | import apsw
11 | import pytest
12 |
13 |
14 | def query(db, *args):
15 | with db:
16 | return db.cursor().execute(*args).fetchone()[0]
17 |
18 |
19 | class TestUUIDv3:
20 | def test_uuid3(self, db, uuid3_examples):
21 | """
22 | Should return expected v3 UUID for known input.
23 | """
24 | for example in uuid3_examples:
25 | args, expected = example
26 | result = query(db, 'SELECT uuid3(?, ?);', args)
27 | u = uuid.UUID(result)
28 | assert u == expected
29 | assert u.version == 3
30 | assert u.variant == uuid.RFC_4122
31 |
32 | def test_uuid3_invalid_namespace(self, db):
33 | """
34 | Should raise an error for invalid or namespace UUID.
35 | """
36 | with pytest.raises(apsw.SQLError) as exc:
37 | query(db, 'SELECT uuid3(?, ?);', ('this is not a UUID', 'test'))
38 | assert 'cannot parse namespace UUID' in str(exc)
39 |
40 |
41 | class TestUUIDv5:
42 | def test_uuid5(self, db, uuid5_examples):
43 | """
44 | Should return expected v5 UUID for known input.
45 | """
46 | for example in uuid5_examples:
47 | args, expected = example
48 | result = query(db, 'SELECT uuid5(?, ?);', args)
49 | u = uuid.UUID(result)
50 | assert u == expected
51 | assert u.version == 5
52 | assert u.variant == uuid.RFC_4122
53 |
54 | def test_uuid5_invalid_namespace(self, db):
55 | """
56 | Should raise an error for invalid or namespace UUID.
57 | """
58 | with pytest.raises(apsw.SQLError) as exc:
59 | query(db, 'SELECT uuid5(?, ?);', ('this is not a UUID', 'test'))
60 | assert 'cannot parse namespace UUID' in str(exc)
61 |
62 | class TestUUIDConversion:
63 | def test_uuid_to_text(self, db, uuid5_examples):
64 | """
65 | Should convert binary UUID (bytes) to canonical string representation.
66 | """
67 | for _, expected in uuid5_examples:
68 | result = query(db, 'SELECT uuid_to_text(?);', (expected.bytes,))
69 | u = uuid.UUID(result)
70 | assert u == expected
71 |
72 | def test_uuid_to_text_invalid_length(self, db, uuid5_examples):
73 | """
74 | Should raise an error if binary UUID data is not 16 bytes long.
75 | """
76 | with pytest.raises(apsw.SQLError) as exc:
77 | buf = secrets.token_bytes(15)
78 | query(db, 'SELECT uuid_to_text(?);', (buf,))
79 | assert 'UUID must be 16 bytes' in str(exc)
80 |
81 | def test_uuid_to_blob(self, db, uuid5_examples):
82 | """
83 | Should convert canonical string UUID to binary (bytes).
84 | """
85 | for _, expected in uuid5_examples:
86 | result = query(db, 'SELECT uuid_to_blob(?);', (str(expected),))
87 | u = uuid.UUID(bytes=result)
88 | assert u == expected
89 |
90 | def test_uuid_to_blob_invalid_uuid(self, db):
91 | """
92 | Should raise an error if string is not UUID.
93 | """
94 | with pytest.raises(apsw.SQLError) as exc:
95 | query(db, 'SELECT uuid_to_blob(?);', ('foo',))
96 | assert 'cannot parse UUID' in str(exc)
97 |
98 |
99 | def test_uuid1(db):
100 | result = query(db, 'SELECT uuid1();')
101 | u = uuid.UUID(result)
102 | assert u.version == 1
103 | assert u.variant == uuid.RFC_4122
104 |
105 |
106 | def test_uuid4(db):
107 | result = query(db, 'SELECT uuid4();')
108 | u = uuid.UUID(result)
109 | assert u.version == 4
110 | assert u.variant == uuid.RFC_4122
111 |
112 |
113 | def test_uuid_nil(db):
114 | result = query(db, 'SELECT uuid_nil();')
115 | u = uuid.UUID(result)
116 | assert u == uuid.UUID('00000000-0000-0000-0000-000000000000')
117 | assert u.variant == uuid.RESERVED_NCS
118 |
119 |
120 | @pytest.mark.parametrize('sql, expected', [
121 | ('SELECT uuid_ns_dns();', uuid.NAMESPACE_DNS),
122 | ('SELECT uuid_ns_oid();', uuid.NAMESPACE_OID),
123 | ('SELECT uuid_ns_url();', uuid.NAMESPACE_URL),
124 | ('SELECT uuid_ns_x500();', uuid.NAMESPACE_X500),
125 | ])
126 | def test_namespace_uuids(db, sql, expected):
127 | result = query(db, sql)
128 | assert uuid.UUID(result) == expected
129 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | skipsdist = True
3 | envlist = py36
4 |
5 | [testenv]
6 | deps =
7 | apsw
8 | pytest
9 | commands =
10 | pytest --verbose {posargs}
11 |
--------------------------------------------------------------------------------