@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 +22 -0
- package/Makefile +6 -0
- package/README.md +91 -0
- package/__tests__/__snapshots__/base32.decode.test.ts.snap +21 -0
- package/__tests__/__snapshots__/base32.encode.test.ts.snap +15 -0
- package/__tests__/base32.decode.test.ts +95 -0
- package/__tests__/base32.encode.test.ts +137 -0
- package/deploy/schemas/base32/procedures/decode.sql +178 -0
- package/deploy/schemas/base32/procedures/encode.sql +266 -0
- package/deploy/schemas/base32/schema.sql +8 -0
- package/jest.config.js +15 -0
- package/launchql-base32.control +8 -0
- package/launchql.plan +7 -0
- package/package.json +28 -0
- package/revert/schemas/base32/procedures/decode.sql +13 -0
- package/revert/schemas/base32/procedures/encode.sql +18 -0
- package/revert/schemas/base32/schema.sql +7 -0
- package/sql/launchql-base32--0.4.6.sql +327 -0
- package/verify/schemas/base32/procedures/decode.sql +7 -0
- package/verify/schemas/base32/procedures/encode.sql +7 -0
- package/verify/schemas/base32/schema.sql +7 -0
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
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;
|
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
|
+
};
|
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,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;
|