@pgpm/base32 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Dan Lynch <pyramation@gmail.com>
4
+ Copyright (c) 2025 Interweb, Inc.
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
package/Makefile ADDED
@@ -0,0 +1,6 @@
1
+ EXTENSION = launchql-base32
2
+ DATA = sql/launchql-base32--0.4.6.sql
3
+
4
+ PG_CONFIG = pg_config
5
+ PGXS := $(shell $(PG_CONFIG) --pgxs)
6
+ include $(PGXS)
package/README.md ADDED
@@ -0,0 +1,91 @@
1
+ # @pgpm/base32
2
+
3
+ RFC4648 Base32 encode/decode in plpgsql
4
+
5
+ # Usage
6
+
7
+ ```sql
8
+ select base32.encode('foo');
9
+ -- MZXW6===
10
+
11
+
12
+ select base32.decode('MZXW6===');
13
+ -- foo
14
+ ```
15
+
16
+ # credits
17
+
18
+ Thanks to
19
+
20
+ https://tools.ietf.org/html/rfc4648
21
+
22
+ https://www.youtube.com/watch?v=Va8FLD-iuTg
23
+
24
+ # Development
25
+
26
+ ## start the postgres db process
27
+
28
+ First you'll want to start the postgres docker (you can also just use `docker-compose up -d`):
29
+
30
+ ```sh
31
+ make up
32
+ ```
33
+
34
+ ## install modules
35
+
36
+ Install modules
37
+
38
+ ```sh
39
+ yarn install
40
+ ```
41
+
42
+ ## install the Postgres extensions
43
+
44
+ Now that the postgres process is running, install the extensions:
45
+
46
+ ```sh
47
+ make install
48
+ ```
49
+
50
+ This basically `ssh`s into the postgres instance with the `packages/` folder mounted as a volume, and installs the bundled sql code as pgxn extensions.
51
+
52
+ ## testing
53
+
54
+ Testing will load all your latest sql changes and create fresh, populated databases for each sqitch module in `packages/`.
55
+
56
+ ```sh
57
+ yarn test:watch
58
+ ```
59
+
60
+ ## building new modules
61
+
62
+ Create a new folder in `packages/`
63
+
64
+ ```sh
65
+ lql init
66
+ ```
67
+
68
+ Then, run a generator:
69
+
70
+ ```sh
71
+ lql generate
72
+ ```
73
+
74
+ You can also add arguments if you already know what you want to do:
75
+
76
+ ```sh
77
+ lql generate schema --schema myschema
78
+ lql generate table --schema myschema --table mytable
79
+ ```
80
+
81
+ ## deploy code as extensions
82
+
83
+ `cd` into `packages/<module>`, and run `lql package`. This will make an sql file in `packages/<module>/sql/` used for `CREATE EXTENSION` calls to install your sqitch module as an extension.
84
+
85
+ ## recursive deploy
86
+
87
+ You can also deploy all modules utilizing versioning as sqtich modules. Remove `--createdb` if you already created your db:
88
+
89
+ ```sh
90
+ lql deploy awesome-db --yes --recursive --createdb
91
+ ```
@@ -0,0 +1,21 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`base32.decode cases INQXI 1`] = `"Cat"`;
4
+
5
+ exports[`base32.decode cases MNUGK3LJON2HE6LJONTXEZLBOQ====== 1`] = `"chemistryisgreat"`;
6
+
7
+ exports[`base32.decode cases MY====== 1`] = `"f"`;
8
+
9
+ exports[`base32.decode cases MZXQ==== 1`] = `"fo"`;
10
+
11
+ exports[`base32.decode cases MZXW6=== 1`] = `"foo"`;
12
+
13
+ exports[`base32.decode cases MZXW6YQ= 1`] = `"foob"`;
14
+
15
+ exports[`base32.decode cases MZXW6YTB 1`] = `"fooba"`;
16
+
17
+ exports[`base32.decode cases MZXW6YTBOI====== 1`] = `"foobar"`;
18
+
19
+ exports[`base32.decode cases case: 1 1`] = `""`;
20
+
21
+ exports[`base32.decode cases mzxw6ytb 1`] = `"fooba"`;
@@ -0,0 +1,15 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`base32.encode case: 1 1`] = `""`;
4
+
5
+ exports[`base32.encode f 1`] = `"MY======"`;
6
+
7
+ exports[`base32.encode fo 1`] = `"MZXQ===="`;
8
+
9
+ exports[`base32.encode foo 1`] = `"MZXW6==="`;
10
+
11
+ exports[`base32.encode foob 1`] = `"MZXW6YQ="`;
12
+
13
+ exports[`base32.encode fooba 1`] = `"MZXW6YTB"`;
14
+
15
+ exports[`base32.encode foobar 1`] = `"MZXW6YTBOI======"`;
@@ -0,0 +1,95 @@
1
+ import { getConnections, PgTestClient } from 'pgsql-test';
2
+ import cases from 'jest-in-case';
3
+
4
+ let pg: PgTestClient;
5
+ let teardown: () => Promise<void>;
6
+
7
+ beforeAll(async () => {
8
+ ({ pg, teardown } = await getConnections());
9
+ });
10
+
11
+ afterAll(async () => {
12
+ await teardown();
13
+ });
14
+
15
+
16
+ it('base32_to_decimal', async () => {
17
+ const { base32_to_decimal } = await pg.one(
18
+ `SELECT base32.base32_to_decimal($1::text) AS base32_to_decimal`,
19
+ ['INQXI===']
20
+ );
21
+ expect(base32_to_decimal).toEqual(['8', '13', '16', '23', '8', '=', '=', '=']);
22
+ });
23
+
24
+ it('decimal_to_chunks', async () => {
25
+ const { decimal_to_chunks } = await pg.one(
26
+ `SELECT base32.decimal_to_chunks($1::text[]) AS decimal_to_chunks`,
27
+ [['8', '13', '16', '23', '8', '=', '=', '=']]
28
+ );
29
+ expect(decimal_to_chunks).toEqual([
30
+ '01000',
31
+ '01101',
32
+ '10000',
33
+ '10111',
34
+ '01000',
35
+ 'xxxxx',
36
+ 'xxxxx',
37
+ 'xxxxx'
38
+ ]);
39
+ });
40
+
41
+ it('decode', async () => {
42
+ const { decode } = await pg.one(
43
+ `SELECT base32.decode($1::text) AS decode`,
44
+ ['INQXI']
45
+ );
46
+ expect(decode).toEqual('Cat');
47
+ });
48
+
49
+ it('zero_fill', async () => {
50
+ const { zero_fill } = await pg.one(
51
+ `SELECT base32.zero_fill($1::int, $2::int) AS zero_fill`,
52
+ [300, 2]
53
+ );
54
+ expect(zero_fill).toBe('75');
55
+ });
56
+
57
+ it('zero_fill (-)', async () => {
58
+ const { zero_fill } = await pg.one(
59
+ `SELECT base32.zero_fill($1::int, $2::int) AS zero_fill`,
60
+ [-300, 2]
61
+ );
62
+ expect(zero_fill).toBe('1073741749');
63
+ });
64
+
65
+ it('zero_fill (0)', async () => {
66
+ const { zero_fill } = await pg.one(
67
+ `SELECT base32.zero_fill($1::int, $2::int) AS zero_fill`,
68
+ [-300, 0]
69
+ );
70
+ expect(zero_fill).toBe('4294966996');
71
+ });
72
+
73
+ cases(
74
+ 'base32.decode cases',
75
+ async (opts: { name: string; result: string }) => {
76
+ const { decode } = await pg.one(
77
+ `SELECT base32.decode($1::text) AS decode`,
78
+ [opts.name]
79
+ );
80
+ expect(decode).toEqual(opts.result);
81
+ expect(decode).toMatchSnapshot();
82
+ },
83
+ [
84
+ { result: '', name: '' },
85
+ { result: 'Cat', name: 'INQXI' },
86
+ { result: 'chemistryisgreat', name: 'MNUGK3LJON2HE6LJONTXEZLBOQ======' },
87
+ { result: 'f', name: 'MY======' },
88
+ { result: 'fo', name: 'MZXQ====' },
89
+ { result: 'foo', name: 'MZXW6===' },
90
+ { result: 'foob', name: 'MZXW6YQ=' },
91
+ { result: 'fooba', name: 'MZXW6YTB' },
92
+ { result: 'fooba', name: 'mzxw6ytb' },
93
+ { result: 'foobar', name: 'MZXW6YTBOI======' }
94
+ ]
95
+ );
@@ -0,0 +1,137 @@
1
+ import { getConnections, PgTestClient } from 'pgsql-test';
2
+ import cases from 'jest-in-case';
3
+
4
+ let pg: PgTestClient;
5
+ let teardown: () => Promise<void>;
6
+
7
+ beforeAll(async () => {
8
+ ({ pg, teardown } = await getConnections());
9
+ });
10
+
11
+ afterAll(async () => {
12
+ await teardown();
13
+ });
14
+
15
+
16
+ it('to_ascii', async () => {
17
+ const { to_ascii } = await pg.one(
18
+ `SELECT base32.to_ascii($1::text) AS to_ascii`,
19
+ ['Cat']
20
+ );
21
+ expect(to_ascii).toEqual([67, 97, 116]);
22
+ });
23
+
24
+ it('to_binary', async () => {
25
+ const { to_ascii } = await pg.one(
26
+ `SELECT base32.to_ascii($1::text) AS to_ascii`,
27
+ ['Cat']
28
+ );
29
+ const { to_binary } = await pg.one(
30
+ `SELECT base32.to_binary($1::int[]) AS to_binary`,
31
+ [to_ascii]
32
+ );
33
+ expect(to_binary).toEqual(['01000011', '01100001', '01110100']);
34
+ });
35
+
36
+ it('to_groups', async () => {
37
+ const { to_groups } = await pg.one(
38
+ `SELECT base32.to_groups($1::text[]) AS to_groups`,
39
+ [['01000011', '01100001', '01110100']]
40
+ );
41
+ expect(to_groups).toEqual([
42
+ '01000011',
43
+ '01100001',
44
+ '01110100',
45
+ 'xxxxxxxx',
46
+ 'xxxxxxxx'
47
+ ]);
48
+ });
49
+
50
+ it('to_chunks', async () => {
51
+ const { to_chunks } = await pg.one(
52
+ `SELECT base32.to_chunks($1::text[]) AS to_chunks`,
53
+ [['01000011', '01100001', '01110100', 'xxxxxxxx', 'xxxxxxxx']]
54
+ );
55
+ expect(to_chunks).toEqual([
56
+ '01000',
57
+ '01101',
58
+ '10000',
59
+ '10111',
60
+ '0100x',
61
+ 'xxxxx',
62
+ 'xxxxx',
63
+ 'xxxxx'
64
+ ]);
65
+ });
66
+
67
+ it('fill_chunks', async () => {
68
+ const { fill_chunks } = await pg.one(
69
+ `SELECT base32.fill_chunks($1::text[]) AS fill_chunks`,
70
+ [[
71
+ '01000',
72
+ '01101',
73
+ '10000',
74
+ '10111',
75
+ '0100x',
76
+ 'xxxxx',
77
+ 'xxxxx',
78
+ 'xxxxx'
79
+ ]]
80
+ );
81
+ expect(fill_chunks).toEqual([
82
+ '01000',
83
+ '01101',
84
+ '10000',
85
+ '10111',
86
+ '01000',
87
+ 'xxxxx',
88
+ 'xxxxx',
89
+ 'xxxxx'
90
+ ]);
91
+ });
92
+
93
+ it('to_decimal', async () => {
94
+ const { to_decimal } = await pg.one(
95
+ `SELECT base32.to_decimal($1::text[]) AS to_decimal`,
96
+ [[
97
+ '01000',
98
+ '01101',
99
+ '10000',
100
+ '10111',
101
+ '01000',
102
+ 'xxxxx',
103
+ 'xxxxx',
104
+ 'xxxxx'
105
+ ]]
106
+ );
107
+ expect(to_decimal).toEqual(['8', '13', '16', '23', '8', '=', '=', '=']);
108
+ });
109
+
110
+ it('to_base32', async () => {
111
+ const { to_base32 } = await pg.one(
112
+ `SELECT base32.to_base32($1::text[]) AS to_base32`,
113
+ [['8', '13', '16', '23', '8', '=', '=', '=']]
114
+ );
115
+ expect(to_base32).toEqual('INQXI===');
116
+ });
117
+
118
+ cases(
119
+ 'base32.encode',
120
+ async (opts: { name: string; result: string }) => {
121
+ const { encode } = await pg.one(
122
+ `SELECT base32.encode($1::text) AS encode`,
123
+ [opts.name]
124
+ );
125
+ expect(encode).toEqual(opts.result);
126
+ expect(encode).toMatchSnapshot();
127
+ },
128
+ [
129
+ { name: '', result: '' },
130
+ { name: 'f', result: 'MY======' },
131
+ { name: 'fo', result: 'MZXQ====' },
132
+ { name: 'foo', result: 'MZXW6===' },
133
+ { name: 'foob', result: 'MZXW6YQ=' },
134
+ { name: 'fooba', result: 'MZXW6YTB' },
135
+ { name: 'foobar', result: 'MZXW6YTBOI======' }
136
+ ]
137
+ );
@@ -0,0 +1,178 @@
1
+ -- Deploy schemas/base32/procedures/decode to pg
2
+
3
+ -- requires: schemas/base32/schema
4
+ -- requires: schemas/base32/procedures/encode
5
+
6
+ BEGIN;
7
+
8
+ -- 'I' => '8'
9
+ CREATE FUNCTION base32.base32_alphabet_to_decimal(
10
+ input text
11
+ ) returns text as $$
12
+ DECLARE
13
+ alphabet text = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
14
+ alpha int;
15
+ BEGIN
16
+ alpha = position(input in alphabet) - 1;
17
+ IF (alpha < 0) THEN
18
+ RETURN '=';
19
+ END IF;
20
+ RETURN alpha::text;
21
+ END;
22
+ $$
23
+ LANGUAGE 'plpgsql' IMMUTABLE;
24
+
25
+ -- INQXI=== => ['8', '13', '16', '23', '8', '=', '=', '=']
26
+ CREATE FUNCTION base32.base32_to_decimal(
27
+ input text
28
+ ) returns text[] as $$
29
+ DECLARE
30
+ i int;
31
+ output text[];
32
+ BEGIN
33
+ input = upper(input);
34
+ FOR i IN 1 .. character_length(input) LOOP
35
+ output = array_append(output, base32.base32_alphabet_to_decimal(substring(input from i for 1)));
36
+ END LOOP;
37
+ RETURN output;
38
+ END;
39
+ $$
40
+ LANGUAGE 'plpgsql' STABLE;
41
+
42
+ -- ['8', '13', '16', '23', '8', '=', '=', '=']
43
+ -- [ '01000', '01101', '10000', '10111', '01000', 'xxxxx', 'xxxxx', 'xxxxx' ]
44
+ CREATE FUNCTION base32.decimal_to_chunks(
45
+ input text[]
46
+ ) returns text[] as $$
47
+ DECLARE
48
+ i int;
49
+ part text;
50
+ output text[];
51
+ BEGIN
52
+ FOR i IN 1 .. cardinality(input) LOOP
53
+ part = input[i];
54
+ IF (part = '=') THEN
55
+ output = array_append(output, 'xxxxx');
56
+ ELSE
57
+ output = array_append(output, right(base32.to_binary(part::int), 5));
58
+ END IF;
59
+ END LOOP;
60
+ RETURN output;
61
+ END;
62
+ $$
63
+ LANGUAGE 'plpgsql' STABLE;
64
+
65
+ CREATE FUNCTION base32.base32_alphabet_to_decimal_int(
66
+ input text
67
+ ) returns int as $$
68
+ DECLARE
69
+ alphabet text = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
70
+ alpha int;
71
+ BEGIN
72
+ alpha = position(input in alphabet) - 1;
73
+ RETURN alpha;
74
+ END;
75
+ $$
76
+ LANGUAGE 'plpgsql' IMMUTABLE;
77
+
78
+ -- this emulates the >>> (unsigned right shift)
79
+ -- https://stackoverflow.com/questions/41134337/unsigned-right-shift-zero-fill-right-shift-in-php-java-javascript-equiv
80
+ CREATE FUNCTION base32.zero_fill(
81
+ a int, b int
82
+ ) returns bigint as $$
83
+ DECLARE
84
+ bin text;
85
+ m int;
86
+ BEGIN
87
+
88
+ IF (b >= 32 OR b < -32) THEN
89
+ m = b/32;
90
+ b = b-(m*32);
91
+ END IF;
92
+
93
+ IF (b < 0) THEN
94
+ b = 32 + b;
95
+ END IF;
96
+
97
+ IF (b = 0) THEN
98
+ return ((a>>1)&2147483647)*2::bigint+((a>>b)&1);
99
+ END IF;
100
+
101
+ IF (a < 0) THEN
102
+ a = (a >> 1);
103
+ a = a & 2147483647; -- 0x7fffffff
104
+ a = a | 1073741824; -- 0x40000000
105
+ a = (a >> (b - 1));
106
+ ELSE
107
+ a = (a >> b);
108
+ END IF;
109
+
110
+ RETURN a;
111
+ END;
112
+ $$
113
+ LANGUAGE 'plpgsql' IMMUTABLE;
114
+
115
+ CREATE FUNCTION base32.valid(
116
+ input text
117
+ ) returns boolean as $$
118
+ BEGIN
119
+ IF (upper(input) ~* '^[A-Z2-7]+=*$') THEN
120
+ RETURN true;
121
+ END IF;
122
+ RETURN false;
123
+ END;
124
+ $$
125
+ LANGUAGE 'plpgsql' IMMUTABLE;
126
+
127
+ CREATE FUNCTION base32.decode(
128
+ input text
129
+ ) returns text as $$
130
+ DECLARE
131
+ i int;
132
+ arr int[];
133
+ output text[];
134
+ len int;
135
+ num int;
136
+
137
+ value int = 0;
138
+ index int = 0;
139
+ bits int = 0;
140
+ BEGIN
141
+ len = character_length(input);
142
+ IF (len = 0) THEN
143
+ RETURN '';
144
+ END IF;
145
+
146
+ IF (NOT base32.valid(input)) THEN
147
+ RAISE EXCEPTION 'INVALID_BASE32';
148
+ END IF;
149
+
150
+ input = replace(input, '=', '');
151
+ input = upper(input);
152
+ len = character_length(input);
153
+ num = len * 5 / 8;
154
+
155
+ select array(select * from generate_series(1,num))
156
+ INTO arr;
157
+
158
+ FOR i IN 1 .. len LOOP
159
+ value = (value << 5) | base32.base32_alphabet_to_decimal_int(substring(input from i for 1));
160
+ bits = bits + 5;
161
+ IF (bits >= 8) THEN
162
+ arr[index] = base32.zero_fill(value, (bits - 8)) & 255; -- arr[index] = (value >>> (bits - 8)) & 255;
163
+ index = index + 1;
164
+ bits = bits - 8;
165
+ END IF;
166
+ END LOOP;
167
+
168
+ len = cardinality(arr);
169
+ FOR i IN 0 .. len-2 LOOP
170
+ output = array_append(output, chr(arr[i]));
171
+ END LOOP;
172
+
173
+ RETURN array_to_string(output, '');
174
+ END;
175
+ $$
176
+ LANGUAGE 'plpgsql' STABLE;
177
+
178
+ COMMIT;
@@ -0,0 +1,266 @@
1
+ -- Deploy schemas/base32/procedures/encode to pg
2
+
3
+ -- requires: schemas/base32/schema
4
+
5
+ -- https://tools.ietf.org/html/rfc4648
6
+ -- https://www.youtube.com/watch?v=Va8FLD-iuTg
7
+
8
+ BEGIN;
9
+
10
+ -- '01000011' => 67
11
+ CREATE FUNCTION base32.binary_to_int(
12
+ input text
13
+ ) returns int as $$
14
+ DECLARE
15
+ i int;
16
+ buf text;
17
+ BEGIN
18
+ buf = 'SELECT B''' || input || '''::int';
19
+ EXECUTE buf INTO i;
20
+ RETURN i;
21
+ END;
22
+ $$
23
+ LANGUAGE 'plpgsql' IMMUTABLE;
24
+
25
+ -- ASCII decimal values Cat => [67,97,116]
26
+ CREATE FUNCTION base32.to_ascii(
27
+ input text
28
+ ) returns int[] as $$
29
+ DECLARE
30
+ i int;
31
+ output int[];
32
+ BEGIN
33
+ FOR i IN 1 .. character_length(input) LOOP
34
+ output = array_append(output, ascii(substring(input from i for 1)));
35
+ END LOOP;
36
+ RETURN output;
37
+ END;
38
+ $$
39
+ LANGUAGE 'plpgsql' IMMUTABLE;
40
+
41
+ -- 67 => '01000011'
42
+ CREATE FUNCTION base32.to_binary(
43
+ input int
44
+ ) returns text as $$
45
+ DECLARE
46
+ i int = 1;
47
+ j int = 0;
48
+ output char[] = ARRAY['x', 'x', 'x', 'x', 'x', 'x', 'x', 'x'];
49
+ BEGIN
50
+ WHILE i < 256 LOOP
51
+ output[8-j] = (CASE WHEN (input & i) > 0 THEN '1' ELSE '0' END)::char;
52
+ i = i << 1;
53
+ j = j + 1;
54
+ END LOOP;
55
+ RETURN array_to_string(output, '');
56
+ END;
57
+ $$
58
+ LANGUAGE 'plpgsql' IMMUTABLE;
59
+
60
+ -- [67,97,116] => [01000011, 01100001, 01110100]
61
+ CREATE FUNCTION base32.to_binary(
62
+ input int[]
63
+ ) returns text[] as $$
64
+ DECLARE
65
+ i int;
66
+ output text[];
67
+ BEGIN
68
+ FOR i IN 1 .. cardinality(input) LOOP
69
+ output = array_append(output, base32.to_binary(input[i]));
70
+ END LOOP;
71
+ RETURN output;
72
+ END;
73
+ $$
74
+ LANGUAGE 'plpgsql' IMMUTABLE;
75
+
76
+ -- convert an input byte stream into group of 5 bytes
77
+ -- if there are less than 5, adding padding
78
+
79
+ -- [01000011, 01100001, 01110100, xxxxxxxx, xxxxxxxx]
80
+
81
+ CREATE FUNCTION base32.to_groups(
82
+ input text[]
83
+ ) returns text[] as $$
84
+ DECLARE
85
+ i int;
86
+ output text[];
87
+ len int = cardinality(input);
88
+ BEGIN
89
+ IF ( len % 5 = 0 ) THEN
90
+ RETURN input;
91
+ END IF;
92
+ FOR i IN 1 .. 5 - (len % 5) LOOP
93
+ input = array_append(input, 'xxxxxxxx');
94
+ END LOOP;
95
+ RETURN input;
96
+ END;
97
+ $$
98
+ LANGUAGE 'plpgsql' IMMUTABLE;
99
+
100
+ -- break these into 5 bit chunks (5 * 8 = 40 bits, when we 40/5 = 8 new elements of 5 bits each)
101
+
102
+ -- [01000, 01101, 10000, 10111, 0100x, xxxxx, xxxxx, xxxxx]
103
+
104
+ CREATE FUNCTION base32.string_nchars(text, integer)
105
+ RETURNS text[] AS $$
106
+ SELECT ARRAY(SELECT substring($1 from n for $2)
107
+ FROM generate_series(1, length($1), $2) n);
108
+ $$ LANGUAGE sql IMMUTABLE;
109
+
110
+ CREATE FUNCTION base32.to_chunks(
111
+ input text[]
112
+ ) returns text[] as $$
113
+ DECLARE
114
+ i int;
115
+ output text[];
116
+ str text;
117
+ len int = cardinality(input);
118
+ BEGIN
119
+ RETURN base32.string_nchars(array_to_string(input, ''), 5);
120
+ END;
121
+ $$
122
+ LANGUAGE 'plpgsql' IMMUTABLE;
123
+
124
+ -- if a chunk has a mix of real bits (0|1) and empty (x), replace x with 0
125
+
126
+ -- [01000, 01101, 10000, 10111, 0100x, xxxxx, xxxxx, xxxxx]
127
+ -- [01000, 01101, 10000, 10111, 01000, xxxxx, xxxxx, xxxxx]
128
+
129
+ CREATE FUNCTION base32.fill_chunks(
130
+ input text[]
131
+ ) returns text[] as $$
132
+ DECLARE
133
+ i int;
134
+ output text[];
135
+ chunk text;
136
+ len int = cardinality(input);
137
+ BEGIN
138
+ FOR i IN 1 .. len LOOP
139
+ chunk = input[i];
140
+ IF (chunk ~* '[0-1]+') THEN
141
+ chunk = replace(chunk, 'x', '0');
142
+ END IF;
143
+ output = array_append(output, chunk);
144
+ END LOOP;
145
+ RETURN output;
146
+ END;
147
+ $$
148
+ LANGUAGE 'plpgsql' IMMUTABLE;
149
+
150
+ -- convert to decimal value
151
+
152
+ -- [01000, 01101, 10000, 10111, 01000, xxxxx, xxxxx, xxxxx]
153
+ -- [0b01000, 0b01101, 0b10000, 0b10111, 0b01000, xxxxx, xxxxx, xxxxx]
154
+ -- [ 8, 13, 16, 23, 8, '=', '=', '=' ]
155
+
156
+ CREATE FUNCTION base32.to_decimal(
157
+ input text[]
158
+ ) returns text[] as $$
159
+ DECLARE
160
+ i int;
161
+ output text[];
162
+ chunk text;
163
+ buf text;
164
+ len int = cardinality(input);
165
+ BEGIN
166
+ FOR i IN 1 .. len LOOP
167
+ chunk = input[i];
168
+ IF (chunk ~* '[x]+') THEN
169
+ chunk = '=';
170
+ ELSE
171
+ chunk = base32.binary_to_int(input[i])::text;
172
+ END IF;
173
+ output = array_append(output, chunk);
174
+ END LOOP;
175
+ RETURN output;
176
+ END;
177
+ $$
178
+ LANGUAGE 'plpgsql' IMMUTABLE;
179
+
180
+
181
+ -- Table 3: The Base 32 Alphabet
182
+
183
+ -- 0 A 9 J 18 S 27 3
184
+ -- 1 B 10 K 19 T 28 4
185
+ -- 2 C 11 L 20 U 29 5
186
+ -- 3 D 12 M 21 V 30 6
187
+ -- 4 E 13 N 22 W 31 7
188
+ -- 5 F 14 O 23 X
189
+ -- 6 G 15 P 24 Y (pad) =
190
+ -- 7 H 16 Q 25 Z
191
+ -- 8 I 17 R 26 2
192
+
193
+ CREATE FUNCTION base32.base32_alphabet(
194
+ input int
195
+ ) returns char as $$
196
+ DECLARE
197
+ alphabet text[] = ARRAY[
198
+ 'A', 'B', 'C', 'D', 'E', 'F',
199
+ 'G', 'H', 'I', 'J', 'K', 'L',
200
+ 'M', 'N', 'O', 'P', 'Q', 'R',
201
+ 'S', 'T', 'U', 'V', 'W', 'X',
202
+ 'Y', 'Z', '2', '3', '4', '5',
203
+ '6', '7'
204
+ ]::text;
205
+ BEGIN
206
+ RETURN alphabet[input+1];
207
+ END;
208
+ $$
209
+ LANGUAGE 'plpgsql' IMMUTABLE;
210
+
211
+ -- [ 8, 13, 16, 23, 8, '=', '=', '=' ]
212
+ -- [ I, N, Q, X, I, '=', '=', '=' ]
213
+
214
+ CREATE FUNCTION base32.to_base32(
215
+ input text[]
216
+ ) returns text as $$
217
+ DECLARE
218
+ i int;
219
+ output text[];
220
+ chunk text;
221
+ buf text;
222
+ len int = cardinality(input);
223
+ BEGIN
224
+ FOR i IN 1 .. len LOOP
225
+ chunk = input[i];
226
+ IF (chunk = '=') THEN
227
+ chunk = '=';
228
+ ELSE
229
+ chunk = base32.base32_alphabet(chunk::int);
230
+ END IF;
231
+ output = array_append(output, chunk);
232
+ END LOOP;
233
+ RETURN array_to_string(output, '');
234
+ END;
235
+ $$
236
+ LANGUAGE 'plpgsql' IMMUTABLE;
237
+
238
+ CREATE FUNCTION base32.encode(
239
+ input text
240
+ ) returns text as $$
241
+ BEGIN
242
+ IF (character_length(input) = 0) THEN
243
+ RETURN '';
244
+ END IF;
245
+
246
+ RETURN
247
+ base32.to_base32(
248
+ base32.to_decimal(
249
+ base32.fill_chunks(
250
+ base32.to_chunks(
251
+ base32.to_groups(
252
+ base32.to_binary(
253
+ base32.to_ascii(
254
+ input
255
+ )
256
+ )
257
+ )
258
+ )
259
+ )
260
+ )
261
+ );
262
+ END;
263
+ $$
264
+ LANGUAGE 'plpgsql' IMMUTABLE;
265
+
266
+ COMMIT;
@@ -0,0 +1,8 @@
1
+ -- Deploy schemas/base32/schema to pg
2
+
3
+
4
+ BEGIN;
5
+
6
+ CREATE SCHEMA base32;
7
+
8
+ COMMIT;
package/jest.config.js ADDED
@@ -0,0 +1,15 @@
1
+ /** @type {import('ts-jest').JestConfigWithTsJest} */
2
+ module.exports = {
3
+ preset: 'ts-jest',
4
+ testEnvironment: 'node',
5
+
6
+ // Match both __tests__ and colocated test files
7
+ testMatch: ['**/?(*.)+(test|spec).{ts,tsx,js,jsx}'],
8
+
9
+ // Ignore build artifacts and type declarations
10
+ testPathIgnorePatterns: ['/dist/', '\\.d\\.ts$'],
11
+ modulePathIgnorePatterns: ['<rootDir>/dist/'],
12
+ watchPathIgnorePatterns: ['/dist/'],
13
+
14
+ moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
15
+ };
@@ -0,0 +1,8 @@
1
+ # launchql-base32 extension
2
+ comment = 'launchql-base32 extension'
3
+ default_version = '0.4.6'
4
+ module_pathname = '$libdir/launchql-base32'
5
+ requires = 'pgcrypto,plpgsql,launchql-verify'
6
+ relocatable = false
7
+ superuser = false
8
+
package/launchql.plan ADDED
@@ -0,0 +1,7 @@
1
+ %syntax-version=1.0.0
2
+ %project=launchql-base32
3
+ %uri=launchql-base32
4
+
5
+ schemas/base32/schema 2017-08-11T08:11:51Z skitch <skitch@5b0c196eeb62> # add schemas/base32/schema
6
+ schemas/base32/procedures/encode [schemas/base32/schema] 2017-08-11T08:11:51Z skitch <skitch@5b0c196eeb62> # add schemas/base32/procedures/encode
7
+ schemas/base32/procedures/decode [schemas/base32/schema schemas/base32/procedures/encode] 2017-08-11T08:11:51Z skitch <skitch@5b0c196eeb62> # add schemas/base32/procedures/decode
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@pgpm/base32",
3
+ "version": "0.4.0",
4
+ "description": "Base32 encoding and decoding functions for PostgreSQL",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "scripts": {
9
+ "bundle": "lql package",
10
+ "test": "jest",
11
+ "test:watch": "jest --watch"
12
+ },
13
+ "dependencies": {
14
+ "@pgpm/verify": "0.4.0"
15
+ },
16
+ "devDependencies": {
17
+ "@launchql/cli": "^4.9.0"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/launchql/extensions"
22
+ },
23
+ "homepage": "https://github.com/launchql/extensions",
24
+ "bugs": {
25
+ "url": "https://github.com/launchql/extensions/issues"
26
+ },
27
+ "gitHead": "cc9f52a335caa6e21ee7751b04b77c84ce6cb809"
28
+ }
@@ -0,0 +1,13 @@
1
+ -- Revert schemas/base32/procedures/decode from pg
2
+
3
+ BEGIN;
4
+
5
+ DROP FUNCTION base32.decode;
6
+ DROP FUNCTION base32.valid;
7
+ DROP FUNCTION base32.zero_fill;
8
+ DROP FUNCTION base32.base32_alphabet_to_decimal_int;
9
+ DROP FUNCTION base32.decimal_to_chunks;
10
+ DROP FUNCTION base32.base32_to_decimal;
11
+ DROP FUNCTION base32.base32_alphabet_to_decimal;
12
+
13
+ COMMIT;
@@ -0,0 +1,18 @@
1
+ -- Revert schemas/base32/procedures/encode from pg
2
+
3
+ BEGIN;
4
+
5
+ DROP FUNCTION base32.encode;
6
+ DROP FUNCTION base32.to_base32;
7
+ DROP FUNCTION base32.base32_alphabet;
8
+ DROP FUNCTION base32.to_decimal;
9
+ DROP FUNCTION base32.fill_chunks;
10
+ DROP FUNCTION base32.to_chunks;
11
+ DROP FUNCTION base32.string_nchars;
12
+ DROP FUNCTION base32.to_groups;
13
+ DROP FUNCTION base32.to_binary(int[]);
14
+ DROP FUNCTION base32.to_binary(int);
15
+ DROP FUNCTION base32.to_ascii;
16
+ DROP FUNCTION base32.binary_to_int;
17
+
18
+ COMMIT;
@@ -0,0 +1,7 @@
1
+ -- Revert schemas/base32/schema from pg
2
+
3
+ BEGIN;
4
+
5
+ DROP SCHEMA base32 CASCADE;
6
+
7
+ COMMIT;
@@ -0,0 +1,327 @@
1
+ \echo Use "CREATE EXTENSION launchql-base32" to load this file. \quit
2
+ CREATE SCHEMA base32;
3
+
4
+ CREATE FUNCTION base32.binary_to_int(input text) RETURNS int AS $EOFCODE$
5
+ DECLARE
6
+ i int;
7
+ buf text;
8
+ BEGIN
9
+ buf = 'SELECT B''' || input || '''::int';
10
+ EXECUTE buf INTO i;
11
+ RETURN i;
12
+ END;
13
+ $EOFCODE$ LANGUAGE plpgsql IMMUTABLE;
14
+
15
+ CREATE FUNCTION base32.to_ascii(input text) RETURNS int[] AS $EOFCODE$
16
+ DECLARE
17
+ i int;
18
+ output int[];
19
+ BEGIN
20
+ FOR i IN 1 .. character_length(input) LOOP
21
+ output = array_append(output, ascii(substring(input from i for 1)));
22
+ END LOOP;
23
+ RETURN output;
24
+ END;
25
+ $EOFCODE$ LANGUAGE plpgsql IMMUTABLE;
26
+
27
+ CREATE FUNCTION base32.to_binary(input int) RETURNS text AS $EOFCODE$
28
+ DECLARE
29
+ i int = 1;
30
+ j int = 0;
31
+ output char[] = ARRAY['x', 'x', 'x', 'x', 'x', 'x', 'x', 'x'];
32
+ BEGIN
33
+ WHILE i < 256 LOOP
34
+ output[8-j] = (CASE WHEN (input & i) > 0 THEN '1' ELSE '0' END)::char;
35
+ i = i << 1;
36
+ j = j + 1;
37
+ END LOOP;
38
+ RETURN array_to_string(output, '');
39
+ END;
40
+ $EOFCODE$ LANGUAGE plpgsql IMMUTABLE;
41
+
42
+ CREATE FUNCTION base32.to_binary(input int[]) RETURNS text[] AS $EOFCODE$
43
+ DECLARE
44
+ i int;
45
+ output text[];
46
+ BEGIN
47
+ FOR i IN 1 .. cardinality(input) LOOP
48
+ output = array_append(output, base32.to_binary(input[i]));
49
+ END LOOP;
50
+ RETURN output;
51
+ END;
52
+ $EOFCODE$ LANGUAGE plpgsql IMMUTABLE;
53
+
54
+ CREATE FUNCTION base32.to_groups(input text[]) RETURNS text[] AS $EOFCODE$
55
+ DECLARE
56
+ i int;
57
+ output text[];
58
+ len int = cardinality(input);
59
+ BEGIN
60
+ IF ( len % 5 = 0 ) THEN
61
+ RETURN input;
62
+ END IF;
63
+ FOR i IN 1 .. 5 - (len % 5) LOOP
64
+ input = array_append(input, 'xxxxxxxx');
65
+ END LOOP;
66
+ RETURN input;
67
+ END;
68
+ $EOFCODE$ LANGUAGE plpgsql IMMUTABLE;
69
+
70
+ CREATE FUNCTION base32.string_nchars(text, int) RETURNS text[] AS $EOFCODE$
71
+ SELECT ARRAY(SELECT substring($1 from n for $2)
72
+ FROM generate_series(1, length($1), $2) n);
73
+ $EOFCODE$ LANGUAGE sql IMMUTABLE;
74
+
75
+ CREATE FUNCTION base32.to_chunks(input text[]) RETURNS text[] AS $EOFCODE$
76
+ DECLARE
77
+ i int;
78
+ output text[];
79
+ str text;
80
+ len int = cardinality(input);
81
+ BEGIN
82
+ RETURN base32.string_nchars(array_to_string(input, ''), 5);
83
+ END;
84
+ $EOFCODE$ LANGUAGE plpgsql IMMUTABLE;
85
+
86
+ CREATE FUNCTION base32.fill_chunks(input text[]) RETURNS text[] AS $EOFCODE$
87
+ DECLARE
88
+ i int;
89
+ output text[];
90
+ chunk text;
91
+ len int = cardinality(input);
92
+ BEGIN
93
+ FOR i IN 1 .. len LOOP
94
+ chunk = input[i];
95
+ IF (chunk ~* '[0-1]+') THEN
96
+ chunk = replace(chunk, 'x', '0');
97
+ END IF;
98
+ output = array_append(output, chunk);
99
+ END LOOP;
100
+ RETURN output;
101
+ END;
102
+ $EOFCODE$ LANGUAGE plpgsql IMMUTABLE;
103
+
104
+ CREATE FUNCTION base32.to_decimal(input text[]) RETURNS text[] AS $EOFCODE$
105
+ DECLARE
106
+ i int;
107
+ output text[];
108
+ chunk text;
109
+ buf text;
110
+ len int = cardinality(input);
111
+ BEGIN
112
+ FOR i IN 1 .. len LOOP
113
+ chunk = input[i];
114
+ IF (chunk ~* '[x]+') THEN
115
+ chunk = '=';
116
+ ELSE
117
+ chunk = base32.binary_to_int(input[i])::text;
118
+ END IF;
119
+ output = array_append(output, chunk);
120
+ END LOOP;
121
+ RETURN output;
122
+ END;
123
+ $EOFCODE$ LANGUAGE plpgsql IMMUTABLE;
124
+
125
+ CREATE FUNCTION base32.base32_alphabet(input int) RETURNS char(1) AS $EOFCODE$
126
+ DECLARE
127
+ alphabet text[] = ARRAY[
128
+ 'A', 'B', 'C', 'D', 'E', 'F',
129
+ 'G', 'H', 'I', 'J', 'K', 'L',
130
+ 'M', 'N', 'O', 'P', 'Q', 'R',
131
+ 'S', 'T', 'U', 'V', 'W', 'X',
132
+ 'Y', 'Z', '2', '3', '4', '5',
133
+ '6', '7'
134
+ ]::text;
135
+ BEGIN
136
+ RETURN alphabet[input+1];
137
+ END;
138
+ $EOFCODE$ LANGUAGE plpgsql IMMUTABLE;
139
+
140
+ CREATE FUNCTION base32.to_base32(input text[]) RETURNS text AS $EOFCODE$
141
+ DECLARE
142
+ i int;
143
+ output text[];
144
+ chunk text;
145
+ buf text;
146
+ len int = cardinality(input);
147
+ BEGIN
148
+ FOR i IN 1 .. len LOOP
149
+ chunk = input[i];
150
+ IF (chunk = '=') THEN
151
+ chunk = '=';
152
+ ELSE
153
+ chunk = base32.base32_alphabet(chunk::int);
154
+ END IF;
155
+ output = array_append(output, chunk);
156
+ END LOOP;
157
+ RETURN array_to_string(output, '');
158
+ END;
159
+ $EOFCODE$ LANGUAGE plpgsql IMMUTABLE;
160
+
161
+ CREATE FUNCTION base32.encode(input text) RETURNS text AS $EOFCODE$
162
+ BEGIN
163
+ IF (character_length(input) = 0) THEN
164
+ RETURN '';
165
+ END IF;
166
+
167
+ RETURN
168
+ base32.to_base32(
169
+ base32.to_decimal(
170
+ base32.fill_chunks(
171
+ base32.to_chunks(
172
+ base32.to_groups(
173
+ base32.to_binary(
174
+ base32.to_ascii(
175
+ input
176
+ )
177
+ )
178
+ )
179
+ )
180
+ )
181
+ )
182
+ );
183
+ END;
184
+ $EOFCODE$ LANGUAGE plpgsql IMMUTABLE;
185
+
186
+ CREATE FUNCTION base32.base32_alphabet_to_decimal(input text) RETURNS text AS $EOFCODE$
187
+ DECLARE
188
+ alphabet text = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
189
+ alpha int;
190
+ BEGIN
191
+ alpha = position(input in alphabet) - 1;
192
+ IF (alpha < 0) THEN
193
+ RETURN '=';
194
+ END IF;
195
+ RETURN alpha::text;
196
+ END;
197
+ $EOFCODE$ LANGUAGE plpgsql IMMUTABLE;
198
+
199
+ CREATE FUNCTION base32.base32_to_decimal(input text) RETURNS text[] AS $EOFCODE$
200
+ DECLARE
201
+ i int;
202
+ output text[];
203
+ BEGIN
204
+ input = upper(input);
205
+ FOR i IN 1 .. character_length(input) LOOP
206
+ output = array_append(output, base32.base32_alphabet_to_decimal(substring(input from i for 1)));
207
+ END LOOP;
208
+ RETURN output;
209
+ END;
210
+ $EOFCODE$ LANGUAGE plpgsql STABLE;
211
+
212
+ CREATE FUNCTION base32.decimal_to_chunks(input text[]) RETURNS text[] AS $EOFCODE$
213
+ DECLARE
214
+ i int;
215
+ part text;
216
+ output text[];
217
+ BEGIN
218
+ FOR i IN 1 .. cardinality(input) LOOP
219
+ part = input[i];
220
+ IF (part = '=') THEN
221
+ output = array_append(output, 'xxxxx');
222
+ ELSE
223
+ output = array_append(output, right(base32.to_binary(part::int), 5));
224
+ END IF;
225
+ END LOOP;
226
+ RETURN output;
227
+ END;
228
+ $EOFCODE$ LANGUAGE plpgsql STABLE;
229
+
230
+ CREATE FUNCTION base32.base32_alphabet_to_decimal_int(input text) RETURNS int AS $EOFCODE$
231
+ DECLARE
232
+ alphabet text = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
233
+ alpha int;
234
+ BEGIN
235
+ alpha = position(input in alphabet) - 1;
236
+ RETURN alpha;
237
+ END;
238
+ $EOFCODE$ LANGUAGE plpgsql IMMUTABLE;
239
+
240
+ CREATE FUNCTION base32.zero_fill(a int, b int) RETURNS bigint AS $EOFCODE$
241
+ DECLARE
242
+ bin text;
243
+ m int;
244
+ BEGIN
245
+
246
+ IF (b >= 32 OR b < -32) THEN
247
+ m = b/32;
248
+ b = b-(m*32);
249
+ END IF;
250
+
251
+ IF (b < 0) THEN
252
+ b = 32 + b;
253
+ END IF;
254
+
255
+ IF (b = 0) THEN
256
+ return ((a>>1)&2147483647)*2::bigint+((a>>b)&1);
257
+ END IF;
258
+
259
+ IF (a < 0) THEN
260
+ a = (a >> 1);
261
+ a = a & 2147483647; -- 0x7fffffff
262
+ a = a | 1073741824; -- 0x40000000
263
+ a = (a >> (b - 1));
264
+ ELSE
265
+ a = (a >> b);
266
+ END IF;
267
+
268
+ RETURN a;
269
+ END;
270
+ $EOFCODE$ LANGUAGE plpgsql IMMUTABLE;
271
+
272
+ CREATE FUNCTION base32.valid(input text) RETURNS boolean AS $EOFCODE$
273
+ BEGIN
274
+ IF (upper(input) ~* '^[A-Z2-7]+=*$') THEN
275
+ RETURN true;
276
+ END IF;
277
+ RETURN false;
278
+ END;
279
+ $EOFCODE$ LANGUAGE plpgsql IMMUTABLE;
280
+
281
+ CREATE FUNCTION base32.decode(input text) RETURNS text AS $EOFCODE$
282
+ DECLARE
283
+ i int;
284
+ arr int[];
285
+ output text[];
286
+ len int;
287
+ num int;
288
+
289
+ value int = 0;
290
+ index int = 0;
291
+ bits int = 0;
292
+ BEGIN
293
+ len = character_length(input);
294
+ IF (len = 0) THEN
295
+ RETURN '';
296
+ END IF;
297
+
298
+ IF (NOT base32.valid(input)) THEN
299
+ RAISE EXCEPTION 'INVALID_BASE32';
300
+ END IF;
301
+
302
+ input = replace(input, '=', '');
303
+ input = upper(input);
304
+ len = character_length(input);
305
+ num = len * 5 / 8;
306
+
307
+ select array(select * from generate_series(1,num))
308
+ INTO arr;
309
+
310
+ FOR i IN 1 .. len LOOP
311
+ value = (value << 5) | base32.base32_alphabet_to_decimal_int(substring(input from i for 1));
312
+ bits = bits + 5;
313
+ IF (bits >= 8) THEN
314
+ arr[index] = base32.zero_fill(value, (bits - 8)) & 255; -- arr[index] = (value >>> (bits - 8)) & 255;
315
+ index = index + 1;
316
+ bits = bits - 8;
317
+ END IF;
318
+ END LOOP;
319
+
320
+ len = cardinality(arr);
321
+ FOR i IN 0 .. len-2 LOOP
322
+ output = array_append(output, chr(arr[i]));
323
+ END LOOP;
324
+
325
+ RETURN array_to_string(output, '');
326
+ END;
327
+ $EOFCODE$ LANGUAGE plpgsql STABLE;
@@ -0,0 +1,7 @@
1
+ -- Verify schemas/base32/procedures/decode on pg
2
+
3
+ BEGIN;
4
+
5
+ SELECT verify_function ('base32.decode');
6
+
7
+ ROLLBACK;
@@ -0,0 +1,7 @@
1
+ -- Verify schemas/base32/procedures/encode on pg
2
+
3
+ BEGIN;
4
+
5
+ SELECT verify_function ('base32.encode');
6
+
7
+ ROLLBACK;
@@ -0,0 +1,7 @@
1
+ -- Verify schemas/base32/schema on pg
2
+
3
+ BEGIN;
4
+
5
+ SELECT verify_schema ('base32');
6
+
7
+ ROLLBACK;