├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── Makefile ├── README.md ├── expected └── pg_hashids.out ├── hashids.c ├── hashids.h ├── pg_hashids--1.0--1.1.sql ├── pg_hashids--1.1--1.2.sql ├── pg_hashids--1.2--1.3.sql ├── pg_hashids--1.2.1--1.3.sql ├── pg_hashids--1.3.sql ├── pg_hashids.c ├── pg_hashids.control └── sql └── pg_hashids.sql /.gitignore: -------------------------------------------------------------------------------- 1 | /.deps/ 2 | /regression.diffs 3 | /regression.out 4 | /results/ 5 | *.so 6 | *.o 7 | *.bc 8 | *.gc* 9 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at vahagn.mkrtchyan@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Magnus Persson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | MODULE_big = pg_hashids 2 | OBJS = pg_hashids.o hashids.o 3 | 4 | REGRESS = pg_hashids 5 | 6 | EXTENSION = pg_hashids 7 | DATA = pg_hashids--1.3.sql \ 8 | pg_hashids--1.2.1--1.3.sql \ 9 | pg_hashids--1.2--1.3.sql \ 10 | pg_hashids--1.1--1.2.sql \ 11 | pg_hashids--1.0--1.1.sql 12 | 13 | PG_CONFIG = pg_config 14 | PGXS := $(shell $(PG_CONFIG) --pgxs) 15 | include $(PGXS) 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | pg_hashids, generate short unique ids from integers 2 | ========================================= 3 | 4 | Hashids is a small open-source library that generates short, unique, non-sequential ids from numbers. 5 | It converts numbers like 347 into strings like “yr8”. 6 | You can also decode those ids back. This is useful in bundling several parameters into one or simply using them as short UIDs. 7 | 8 | You can use hashids to hide primary keys in your database. I've used the extension on several production databases. 9 | 10 | Tested PostgreSQL versions : 9.5.X and 9.6.X (Should work on older versions, just not tested) 11 | 12 | It's using [hashids.c](https://github.com/tzvetkoff/hashids.c) under the hood. More information about hashids and it's implementations here: [hashids.org](http://hashids.org) 13 | 14 | Installation 15 | ============ 16 | 17 | Make sure you have development packages installed for postgres and 18 | build tools in general. 19 | 20 | ```bash 21 | $ git clone https://github.com/iCyberon/pg_hashids 22 | $ cd pg_hashids 23 | # Make sure your path includes the bin directory that contains the correct `pg_config` 24 | $ PATH=/path/to/pg/bin:$PATH 25 | $ USE_PGXS=1 make 26 | $ USE_PGXS=1 make install 27 | ``` 28 | 29 | Then in a psql session issue: 30 | 31 | ```sql 32 | CREATE extension pg_hashids; 33 | ``` 34 | 35 | Update 36 | ============ 37 | 38 | Install as usual. 39 | 40 | ```bash 41 | $ git clone https://github.com/iCyberon/pg_hashids 42 | $ cd pg_hashids 43 | # Make sure your path includes the bin directory that contains the correct `pg_config` 44 | $ PATH=/path/to/pg/bin:$PATH 45 | $ USE_PGXS=1 make 46 | $ USE_PGXS=1 make install 47 | ``` 48 | 49 | Then in a psql session issue: 50 | 51 | ```sql 52 | ALTER EXTENSION pg_hashids UPDATE; 53 | ``` 54 | 55 | or 56 | 57 | ```sql 58 | DROP EXTENSION pg_hashids; 59 | CREATE EXTENSION pg_hashids; 60 | ``` 61 | 62 | Check 63 | 64 | ```sql 65 | SELECT default_version, installed_version FROM pg_available_extensions WHERE name = 'pg_hashids'; 66 | ``` 67 | 68 | Tests 69 | ============ 70 | 71 | ```bash 72 | $ USE_PGXS=1 make installcheck 73 | ``` 74 | 75 | Usage 76 | ============ 77 | #### Encoding 78 | Returns a hash using the default `alphabet` and empty `salt`. 79 | 80 | ```sql 81 | SELECT id_encode(1001); -- Result: jNl 82 | ``` 83 | 84 | Returns a hash using the default `alphabet` and supplied `salt`. 85 | 86 | ```sql 87 | SELECT id_encode(1234567, 'This is my salt'); -- Result: Pdzxp 88 | ``` 89 | 90 | Returns a hash using the default `alphabet`, `salt` and minimum hash length. 91 | 92 | ```sql 93 | SELECT id_encode(1234567, 'This is my salt', 10); -- Result: PlRPdzxpR7 94 | ``` 95 | 96 | 97 | Returns a hash using the supplied `alphabet`, `salt` and minimum hash length. 98 | 99 | ```sql 100 | SELECT id_encode(1234567, 'This is my salt', 10, 'abcdefghijABCDxFGHIJ1234567890'); -- Result: 3GJ956J9B9 101 | ``` 102 | 103 | #### Decoding 104 | You can also decode previously generated hashes. Just use the same `salt`, otherwise you'll get wrong results. 105 | 106 | ```sql 107 | SELECT id_decode('PlRPdzxpR7', 'This is my salt', 10); -- Result: 1234567 108 | ``` 109 | 110 | Using a custom alphabet 111 | 112 | ```sql 113 | SELECT id_decode('3GJ956J9B9', 'This is my salt', 10, 'abcdefghijABCDxFGHIJ1234567890'); -- Result: 1234567 114 | ``` 115 | 116 | #### Decoding a single id 117 | You can also decode a previously generated hash into a single integer. Just use the same `salt`, otherwise you'll get wrong results. 118 | The `id_decode_once` function is useful when you expect only one number to be encoded in the hash. 119 | 120 | ```sql 121 | SELECT id_decode_once('jNl'); -- Result: 1001 122 | ``` 123 | 124 | Using salt: 125 | ```sql 126 | SELECT id_decode_once('Pdzxp', 'This is my salt'); -- Result: 1234567 127 | ``` 128 | 129 | Using salt and min_length: 130 | ```sql 131 | SELECT id_decode_once('PlRPdzxpR7', 'This is my salt', 10); -- Result: 1234567 132 | ``` 133 | 134 | Using a custom alphabet: 135 | ```sql 136 | SELECT id_decode_once('3GJ956J9B9', 'This is my salt', 10, 'abcdefghijABCDxFGHIJ1234567890'); -- Result: 1234567 137 | ``` 138 | 139 | -------------------------------------------------------------------------------- /expected/pg_hashids.out: -------------------------------------------------------------------------------- 1 | \set VERBOSITY terse 2 | CREATE EXTENSION pg_hashids; 3 | -- Encoding tests 4 | SELECT id_encode(1001); -- Result: jNl 5 | id_encode 6 | ----------- 7 | jNl 8 | (1 row) 9 | 10 | SELECT id_encode(1234567, 'This is my salt'); -- Result: Pdzxp 11 | id_encode 12 | ----------- 13 | Pdzxp 14 | (1 row) 15 | 16 | SELECT id_encode(1234567, 'This is my salt', 10); -- Result: PlRPdzxpR7 17 | id_encode 18 | ------------ 19 | PlRPdzxpR7 20 | (1 row) 21 | 22 | SELECT id_encode(1234567, 'This is my salt', 10, 'abcdefghijABCDxFGHIJ1234567890'); -- Result: 3GJ956J9B9 23 | id_encode 24 | ------------ 25 | 3GJ956J9B9 26 | (1 row) 27 | 28 | -- Decoding tests 29 | SELECT id_decode('jNl'); -- Result: {1001} 30 | id_decode 31 | ----------- 32 | {1001} 33 | (1 row) 34 | 35 | SELECT id_decode('Pdzxp', 'This is my salt'); -- Result: {1234567} 36 | id_decode 37 | ----------- 38 | {1234567} 39 | (1 row) 40 | 41 | SELECT id_decode('PlRPdzxpR7', 'This is my salt', 10); -- Result: {1234567} 42 | id_decode 43 | ----------- 44 | {1234567} 45 | (1 row) 46 | 47 | SELECT id_decode('3GJ956J9B9', 'This is my salt', 10, 'abcdefghijABCDxFGHIJ1234567890'); -- Result: {1234567} 48 | id_decode 49 | ----------- 50 | {1234567} 51 | (1 row) 52 | 53 | SELECT id_decode_once('jNl'); -- Result: 1001 54 | id_decode_once 55 | ---------------- 56 | 1001 57 | (1 row) 58 | 59 | SELECT id_decode_once('Pdzxp', 'This is my salt'); -- Result: 1234567 60 | id_decode_once 61 | ---------------- 62 | 1234567 63 | (1 row) 64 | 65 | SELECT id_decode_once('PlRPdzxpR7', 'This is my salt', 10); -- Result: 1234567 66 | id_decode_once 67 | ---------------- 68 | 1234567 69 | (1 row) 70 | 71 | SELECT id_decode_once('3GJ956J9B9', 'This is my salt', 10, 'abcdefghijABCDxFGHIJ1234567890'); -- Result: 1234567 72 | id_decode_once 73 | ---------------- 74 | 1234567 75 | (1 row) 76 | 77 | -------------------------------------------------------------------------------- /hashids.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "hashids.h" 8 | #include "postgres.h" 9 | 10 | /* branch prediction hinting */ 11 | #ifndef __has_builtin 12 | # define __has_builtin(x) (0) 13 | #endif 14 | #if defined(__builtin_expect) || __has_builtin(__builtin_expect) 15 | # define HASHIDS_LIKELY(x) (__builtin_expect(!!(x), 1)) 16 | # define HASHIDS_UNLIKELY(x) (__builtin_expect(!!(x), 0)) 17 | #else 18 | # define HASHIDS_LIKELY(x) (x) 19 | # define HASHIDS_UNLIKELY(x) (x) 20 | #endif 21 | 22 | /* fallthrough warning suppression */ 23 | #ifndef __has_feature 24 | # define __has_feature(x) (0) 25 | #endif 26 | #if __has_feature(fallthrough) 27 | # define ATTRIBUTE_FALLTHROUGH __attribute__((fallthrough)) 28 | #else 29 | # define ATTRIBUTE_FALLTHROUGH 30 | #endif 31 | 32 | /* thread-local storage */ 33 | #ifndef TLS 34 | #define TLS 35 | #endif 36 | 37 | /* thread-safe hashids_errno indirection */ 38 | TLS int __hashids_errno_val; 39 | int * 40 | __hashids_errno_addr() 41 | { 42 | return &__hashids_errno_val; 43 | } 44 | 45 | /* default alloc() implementation */ 46 | static inline void * 47 | hashids_alloc_f(size_t size) 48 | { 49 | return palloc0(size); 50 | } 51 | 52 | /* default free() implementation */ 53 | static inline void 54 | hashids_free_f(void *ptr) 55 | { 56 | pfree(ptr); 57 | } 58 | 59 | void *(*_hashids_alloc)(size_t size) = hashids_alloc_f; 60 | void (*_hashids_free)(void *ptr) = hashids_free_f; 61 | 62 | /* fast ceil(x / y) for size_t arguments */ 63 | static inline size_t 64 | hashids_div_ceil_size_t(size_t x, size_t y) 65 | { 66 | return x / y + !!(x % y); 67 | } 68 | 69 | /* fast ceil(x / y) for unsigned short arguments */ 70 | static inline unsigned short 71 | hashids_div_ceil_unsigned_short(unsigned short x, unsigned short y) { 72 | return x / y + !!(x % y); 73 | } 74 | 75 | /* fast log2(x) for unsigned long long */ 76 | const unsigned short hashids_log2_64_tab[64] = { 77 | 63, 0, 58, 1, 59, 47, 53, 2, 78 | 60, 39, 48, 27, 54, 33, 42, 3, 79 | 61, 51, 37, 40, 49, 18, 28, 20, 80 | 55, 30, 34, 11, 43, 14, 22, 4, 81 | 62, 57, 46, 52, 38, 26, 32, 41, 82 | 50, 36, 17, 19, 29, 10, 13, 21, 83 | 56, 45, 25, 31, 35, 16, 9, 12, 84 | 44, 24, 15, 8, 23, 7, 6, 5 85 | }; 86 | 87 | static inline unsigned short 88 | hashids_log2_64(unsigned long long x) 89 | { 90 | x |= x >> 1; 91 | x |= x >> 2; 92 | x |= x >> 4; 93 | x |= x >> 8; 94 | x |= x >> 16; 95 | x |= x >> 32; 96 | 97 | /* pure evil : ieee abuse */ 98 | return hashids_log2_64_tab[ 99 | ((unsigned long long)((x - (x >> 1)) * 0x07EDD5E59A4E28C2)) >> 58]; 100 | } 101 | 102 | /* shuffle loop step */ 103 | #define hashids_shuffle_step(iter) \ 104 | if (i == 0) { break; } \ 105 | if (v == salt_length) { v = 0; } \ 106 | p += salt[v]; j = (salt[v] + v + p) % (iter); \ 107 | temp = str[(iter)]; str[(iter)] = str[j]; str[j] = temp; \ 108 | --i; ++v; 109 | 110 | /* consistent shuffle */ 111 | void 112 | hashids_shuffle(char *str, size_t str_length, char *salt, size_t salt_length) 113 | { 114 | ssize_t i; 115 | size_t j, v, p; 116 | char temp; 117 | 118 | /* meh, meh */ 119 | if (!salt_length) { 120 | return; 121 | } 122 | 123 | /* pure evil : loop unroll */ 124 | for (i = str_length - 1, v = 0, p = 0; i > 0; /* empty */) { 125 | switch (i % 32) { 126 | case 31: hashids_shuffle_step(i); 127 | /* fall through */ 128 | case 30: hashids_shuffle_step(i); 129 | /* fall through */ 130 | case 29: hashids_shuffle_step(i); 131 | /* fall through */ 132 | case 28: hashids_shuffle_step(i); 133 | /* fall through */ 134 | case 27: hashids_shuffle_step(i); 135 | /* fall through */ 136 | case 26: hashids_shuffle_step(i); 137 | /* fall through */ 138 | case 25: hashids_shuffle_step(i); 139 | /* fall through */ 140 | case 24: hashids_shuffle_step(i); 141 | /* fall through */ 142 | case 23: hashids_shuffle_step(i); 143 | /* fall through */ 144 | case 22: hashids_shuffle_step(i); 145 | /* fall through */ 146 | case 21: hashids_shuffle_step(i); 147 | /* fall through */ 148 | case 20: hashids_shuffle_step(i); 149 | /* fall through */ 150 | case 19: hashids_shuffle_step(i); 151 | /* fall through */ 152 | case 18: hashids_shuffle_step(i); 153 | /* fall through */ 154 | case 17: hashids_shuffle_step(i); 155 | /* fall through */ 156 | case 16: hashids_shuffle_step(i); 157 | /* fall through */ 158 | case 15: hashids_shuffle_step(i); 159 | /* fall through */ 160 | case 14: hashids_shuffle_step(i); 161 | /* fall through */ 162 | case 13: hashids_shuffle_step(i); 163 | /* fall through */ 164 | case 12: hashids_shuffle_step(i); 165 | /* fall through */ 166 | case 11: hashids_shuffle_step(i); 167 | /* fall through */ 168 | case 10: hashids_shuffle_step(i); 169 | /* fall through */ 170 | case 9: hashids_shuffle_step(i); 171 | /* fall through */ 172 | case 8: hashids_shuffle_step(i); 173 | /* fall through */ 174 | case 7: hashids_shuffle_step(i); 175 | /* fall through */ 176 | case 6: hashids_shuffle_step(i); 177 | /* fall through */ 178 | case 5: hashids_shuffle_step(i); 179 | /* fall through */ 180 | case 4: hashids_shuffle_step(i); 181 | /* fall through */ 182 | case 3: hashids_shuffle_step(i); 183 | /* fall through */ 184 | case 2: hashids_shuffle_step(i); 185 | /* fall through */ 186 | case 1: hashids_shuffle_step(i); 187 | /* fall through */ 188 | case 0: hashids_shuffle_step(i); 189 | } 190 | } 191 | } 192 | 193 | /* "destructor" */ 194 | void 195 | hashids_free(hashids_t *hashids) 196 | { 197 | if (hashids) { 198 | if (hashids->alphabet) { 199 | _hashids_free(hashids->alphabet); 200 | } 201 | if (hashids->alphabet_copy_1) { 202 | _hashids_free(hashids->alphabet_copy_1); 203 | } 204 | if (hashids->alphabet_copy_2) { 205 | _hashids_free(hashids->alphabet_copy_2); 206 | } 207 | if (hashids->salt) { 208 | _hashids_free(hashids->salt); 209 | } 210 | if (hashids->separators) { 211 | _hashids_free(hashids->separators); 212 | } 213 | if (hashids->guards) { 214 | _hashids_free(hashids->guards); 215 | } 216 | 217 | _hashids_free(hashids); 218 | } 219 | } 220 | 221 | /* common init */ 222 | hashids_t * 223 | hashids_init3(const char *salt, size_t min_hash_length, const char *alphabet) 224 | { 225 | hashids_t *result; 226 | size_t i, j, len; 227 | char ch, *p; 228 | 229 | hashids_errno = HASHIDS_ERROR_OK; 230 | 231 | /* allocate the structure */ 232 | result = (hashids_t *)_hashids_alloc(sizeof(hashids_t)); 233 | if (HASHIDS_UNLIKELY(!result)) { 234 | hashids_errno = HASHIDS_ERROR_ALLOC; 235 | return NULL; 236 | } 237 | 238 | /* allocate enough space for the alphabet */ 239 | len = strlen(alphabet) + 1; 240 | result->alphabet = (char *)_hashids_alloc(len); 241 | 242 | /* extract only the unique characters */ 243 | result->alphabet[0] = '\0'; 244 | for (i = 0, j = 0; i < len; ++i) { 245 | ch = alphabet[i]; 246 | if (!strchr(result->alphabet, ch)) { 247 | result->alphabet[j++] = ch; 248 | } 249 | } 250 | result->alphabet[j] = '\0'; 251 | 252 | /* store alphabet length */ 253 | result->alphabet_length = j; 254 | 255 | /* check length and whitespace */ 256 | if (result->alphabet_length < HASHIDS_MIN_ALPHABET_LENGTH) { 257 | hashids_free(result); 258 | hashids_errno = HASHIDS_ERROR_ALPHABET_LENGTH; 259 | return NULL; 260 | } 261 | if (strchr(result->alphabet, 0x20) || strchr(result->alphabet, 0x09)) { 262 | hashids_free(result); 263 | hashids_errno = HASHIDS_ERROR_ALPHABET_SPACE; 264 | return NULL; 265 | } 266 | 267 | /* copy salt */ 268 | result->salt_length = salt ? strlen(salt) : 0; 269 | result->salt = (char *)_hashids_alloc(result->salt_length + 1); 270 | if (HASHIDS_UNLIKELY(!result->salt)) { 271 | hashids_free(result); 272 | hashids_errno = HASHIDS_ERROR_ALLOC; 273 | return NULL; 274 | } 275 | strncpy(result->salt, salt, result->salt_length); 276 | 277 | /* allocate enough space for separators */ 278 | len = strlen(HASHIDS_DEFAULT_SEPARATORS); 279 | j = (size_t) 280 | (ceil((float)result->alphabet_length / HASHIDS_SEPARATOR_DIVISOR) + 1); 281 | if (j < len + 1) { 282 | j = len + 1; 283 | } 284 | 285 | result->separators = (char *)_hashids_alloc(j); 286 | if (HASHIDS_UNLIKELY(!result->separators)) { 287 | hashids_free(result); 288 | hashids_errno = HASHIDS_ERROR_ALLOC; 289 | return NULL; 290 | } 291 | 292 | /* take default separators out of the alphabet */ 293 | for (i = 0, j = 0; i < strlen(HASHIDS_DEFAULT_SEPARATORS); ++i) { 294 | ch = HASHIDS_DEFAULT_SEPARATORS[i]; 295 | 296 | /* check if separator is actually in the used alphabet */ 297 | if ((p = strchr(result->alphabet, ch))) { 298 | result->separators[j++] = ch; 299 | 300 | /* remove that separator */ 301 | memmove(p, p + 1, 302 | strlen(result->alphabet) - (p - result->alphabet)); 303 | } 304 | } 305 | 306 | /* store separators length */ 307 | result->separators_count = j; 308 | 309 | /* subtract separators count from alphabet length */ 310 | result->alphabet_length -= result->separators_count; 311 | 312 | /* shuffle the separators */ 313 | if (result->separators_count) { 314 | hashids_shuffle(result->separators, result->separators_count, 315 | result->salt, result->salt_length); 316 | } 317 | 318 | /* check if we have any/enough separators */ 319 | if (!result->separators_count 320 | || (((float)result->alphabet_length / (float)result->separators_count) 321 | > HASHIDS_SEPARATOR_DIVISOR)) { 322 | size_t separators_count = (size_t)ceil( 323 | (float)result->alphabet_length / HASHIDS_SEPARATOR_DIVISOR); 324 | 325 | if (separators_count == 1) { 326 | separators_count = 2; 327 | } 328 | 329 | if (separators_count > result->separators_count) { 330 | /* we need more separators - get some from alphabet */ 331 | size_t diff = separators_count - result->separators_count; 332 | strncat(result->separators, result->alphabet, diff); 333 | memmove(result->alphabet, result->alphabet + diff, 334 | result->alphabet_length - diff + 1); 335 | 336 | result->separators_count += diff; 337 | result->alphabet_length -= diff; 338 | } else { 339 | /* we have more than enough - truncate */ 340 | result->separators[separators_count] = '\0'; 341 | result->separators_count = separators_count; 342 | } 343 | } 344 | 345 | /* shuffle alphabet */ 346 | hashids_shuffle(result->alphabet, result->alphabet_length, 347 | result->salt, result->salt_length); 348 | 349 | /* allocate guards */ 350 | result->guards_count = hashids_div_ceil_size_t(result->alphabet_length, 351 | HASHIDS_GUARD_DIVISOR); 352 | result->guards = (char *)_hashids_alloc(result->guards_count + 1); 353 | if (HASHIDS_UNLIKELY(!result->guards)) { 354 | hashids_free(result); 355 | hashids_errno = HASHIDS_ERROR_ALLOC; 356 | return NULL; 357 | } 358 | 359 | if (HASHIDS_UNLIKELY(result->alphabet_length < 3)) { 360 | /* take some from separators */ 361 | strncpy(result->guards, result->separators, result->guards_count); 362 | memmove(result->separators, result->separators + result->guards_count, 363 | result->separators_count - result->guards_count + 1); 364 | 365 | result->separators_count -= result->guards_count; 366 | } else { 367 | /* take them from alphabet */ 368 | strncpy(result->guards, result->alphabet, result->guards_count); 369 | memmove(result->alphabet, result->alphabet + result->guards_count, 370 | result->alphabet_length - result->guards_count + 1); 371 | 372 | result->alphabet_length -= result->guards_count; 373 | } 374 | 375 | /* allocate enough space for the alphabet copies */ 376 | result->alphabet_copy_1 = (char *)_hashids_alloc(result->alphabet_length + 377 | 1); 378 | result->alphabet_copy_2 = (char *)_hashids_alloc(result->alphabet_length + 379 | 1); 380 | if (HASHIDS_UNLIKELY(!result->alphabet || !result->alphabet_copy_1 381 | || !result->alphabet_copy_2)) { 382 | hashids_free(result); 383 | hashids_errno = HASHIDS_ERROR_ALLOC; 384 | return NULL; 385 | } 386 | 387 | /* set min hash length */ 388 | result->min_hash_length = min_hash_length; 389 | 390 | /* return result happily */ 391 | return result; 392 | } 393 | 394 | /* init with salt and minimum hash length */ 395 | hashids_t * 396 | hashids_init2(const char *salt, size_t min_hash_length) 397 | { 398 | return hashids_init3(salt, min_hash_length, HASHIDS_DEFAULT_ALPHABET); 399 | } 400 | 401 | /* init with hash only */ 402 | hashids_t * 403 | hashids_init(const char *salt) 404 | { 405 | return hashids_init2(salt, HASHIDS_DEFAULT_MIN_HASH_LENGTH); 406 | } 407 | 408 | /* estimate buffer size (generic) */ 409 | size_t 410 | hashids_estimate_encoded_size(hashids_t *hashids, 411 | size_t numbers_count, unsigned long long *numbers) 412 | { 413 | size_t i, result_len; 414 | 415 | for (i = 0, result_len = 1; i < numbers_count; ++i) { 416 | if (numbers[i] == 0) { 417 | result_len += 2; 418 | } else if (numbers[i] == 0xFFFFFFFFFFFFFFFFull) { 419 | result_len += hashids_div_ceil_unsigned_short( 420 | hashids_log2_64(numbers[i]), 421 | hashids_log2_64(hashids->alphabet_length)) - 1; 422 | } else { 423 | result_len += hashids_div_ceil_unsigned_short( 424 | hashids_log2_64(numbers[i] + 1), 425 | hashids_log2_64(hashids->alphabet_length)); 426 | } 427 | } 428 | 429 | if (numbers_count > 1) { 430 | result_len += numbers_count - 1; 431 | } 432 | 433 | if (result_len < hashids->min_hash_length) { 434 | result_len = hashids->min_hash_length; 435 | } 436 | 437 | return result_len + 2 /* fast log2 & ceil sometimes undershoot by 1 */; 438 | } 439 | 440 | /* estimate buffer size (variadic) */ 441 | size_t 442 | hashids_estimate_encoded_size_v(hashids_t *hashids, 443 | size_t numbers_count, ...) 444 | { 445 | size_t i, result; 446 | unsigned long long *numbers; 447 | va_list ap; 448 | 449 | numbers = (unsigned long long *)_hashids_alloc(numbers_count * 450 | sizeof(unsigned long long)); 451 | 452 | if (HASHIDS_UNLIKELY(!numbers)) { 453 | hashids_errno = HASHIDS_ERROR_ALLOC; 454 | return 0; 455 | } 456 | 457 | va_start(ap, numbers_count); 458 | for (i = 0; i < numbers_count; ++i) { 459 | numbers[i] = va_arg(ap, unsigned long long); 460 | } 461 | va_end(ap); 462 | 463 | result = hashids_estimate_encoded_size(hashids, numbers_count, numbers); 464 | _hashids_free(numbers); 465 | 466 | return result; 467 | } 468 | 469 | /* encode many (generic) */ 470 | size_t 471 | hashids_encode(hashids_t *hashids, char *buffer, 472 | size_t numbers_count, unsigned long long *numbers) 473 | { 474 | /* bail out if no numbers */ 475 | if (HASHIDS_UNLIKELY(!numbers_count)) { 476 | buffer[0] = '\0'; 477 | 478 | return 0; 479 | } 480 | 481 | size_t i, j, result_len, guard_index, half_length_ceil, half_length_floor; 482 | unsigned long long number, number_copy, numbers_hash; 483 | int p_max; 484 | char lottery, ch, temp_ch, *p, *buffer_end, *buffer_temp; 485 | 486 | /* return an estimation if no buffer */ 487 | if (HASHIDS_UNLIKELY(!buffer)) { 488 | return hashids_estimate_encoded_size(hashids, numbers_count, numbers); 489 | } 490 | 491 | /* copy the alphabet into internal buffer 1 */ 492 | strncpy(hashids->alphabet_copy_1, hashids->alphabet, 493 | hashids->alphabet_length); 494 | 495 | /* walk arguments once and generate a hash */ 496 | for (i = 0, numbers_hash = 0; i < numbers_count; ++i) { 497 | number = numbers[i]; 498 | numbers_hash += number % (i + 100); 499 | } 500 | 501 | /* lottery character */ 502 | lottery = hashids->alphabet[numbers_hash % hashids->alphabet_length]; 503 | 504 | /* start output buffer with it (or don't) */ 505 | buffer[0] = lottery; 506 | buffer_end = buffer + 1; 507 | 508 | /* alphabet-like buffer used for salt at each iteration */ 509 | hashids->alphabet_copy_2[0] = lottery; 510 | hashids->alphabet_copy_2[1] = '\0'; 511 | strncat(hashids->alphabet_copy_2, hashids->salt, 512 | hashids->alphabet_length - 1); 513 | p = hashids->alphabet_copy_2 + hashids->salt_length + 1; 514 | p_max = hashids->alphabet_length - 1 - hashids->salt_length; 515 | if (p_max > 0) { 516 | strncat(hashids->alphabet_copy_2, hashids->alphabet, 517 | p_max); 518 | } else { 519 | hashids->alphabet_copy_2[hashids->alphabet_length] = '\0'; 520 | } 521 | 522 | for (i = 0; i < numbers_count; ++i) { 523 | /* take number */ 524 | number = number_copy = numbers[i]; 525 | 526 | /* create a salt for this iteration */ 527 | if (p_max > 0) { 528 | strncpy(p, hashids->alphabet_copy_1, p_max); 529 | } 530 | 531 | /* shuffle the alphabet */ 532 | hashids_shuffle(hashids->alphabet_copy_1, hashids->alphabet_length, 533 | hashids->alphabet_copy_2, hashids->alphabet_length); 534 | 535 | /* hash the number */ 536 | buffer_temp = buffer_end; 537 | do { 538 | ch = hashids->alphabet_copy_1[number % hashids->alphabet_length]; 539 | *buffer_end++ = ch; 540 | number /= hashids->alphabet_length; 541 | } while (number); 542 | 543 | /* reverse the hash we got */ 544 | for (j = 0; j < (size_t)((buffer_end - buffer_temp) / 2); ++j) { 545 | temp_ch = *(buffer_temp + j); 546 | *(buffer_temp + j) = *(buffer_end - 1 - j); 547 | *(buffer_end - 1 - j) = temp_ch; 548 | } 549 | 550 | if (i + 1 < numbers_count) { 551 | number_copy %= ch + i; 552 | *buffer_end = hashids->separators[number_copy % 553 | hashids->separators_count]; 554 | ++buffer_end; 555 | } 556 | } 557 | 558 | /* intermediate string length */ 559 | result_len = buffer_end - buffer; 560 | 561 | if (result_len < hashids->min_hash_length) { 562 | /* add a guard before the encoded numbers */ 563 | guard_index = (numbers_hash + buffer[0]) % hashids->guards_count; 564 | memmove(buffer + 1, buffer, result_len); 565 | buffer[0] = hashids->guards[guard_index]; 566 | ++result_len; 567 | 568 | if (result_len < hashids->min_hash_length) { 569 | /* add a guard after the encoded numbers */ 570 | guard_index = (numbers_hash + buffer[2]) % hashids->guards_count; 571 | buffer[result_len] = hashids->guards[guard_index]; 572 | ++result_len; 573 | 574 | /* pad with half alphabet before and after */ 575 | half_length_ceil = hashids_div_ceil_size_t( 576 | hashids->alphabet_length, 2); 577 | half_length_floor = floor((float)hashids->alphabet_length / 2); 578 | 579 | /* pad, pad, pad */ 580 | while (result_len < hashids->min_hash_length) { 581 | /* shuffle the alphabet */ 582 | strncpy(hashids->alphabet_copy_2, hashids->alphabet_copy_1, 583 | hashids->alphabet_length); 584 | hashids_shuffle(hashids->alphabet_copy_1, 585 | hashids->alphabet_length, hashids->alphabet_copy_2, 586 | hashids->alphabet_length); 587 | 588 | /* left pad from the end of the alphabet */ 589 | i = hashids_div_ceil_size_t( 590 | hashids->min_hash_length - result_len, 2); 591 | /* right pad from the beginning */ 592 | j = floor((float)(hashids->min_hash_length - result_len) / 2); 593 | 594 | /* check bounds */ 595 | if (i > half_length_ceil) { 596 | i = half_length_ceil; 597 | } 598 | if (j > half_length_floor) { 599 | j = half_length_floor; 600 | } 601 | 602 | /* handle excessively excessive excess */ 603 | if ((i + j) % 2 == 0 && hashids->alphabet_length % 2 == 1) { 604 | ++i; --j; 605 | } 606 | 607 | /* move the current result to "center" */ 608 | memmove(buffer + i, buffer, result_len); 609 | /* pad left */ 610 | memmove(buffer, 611 | hashids->alphabet_copy_1 + hashids->alphabet_length - i, i); 612 | /* pad right */ 613 | memmove(buffer + i + result_len, hashids->alphabet_copy_1, j); 614 | 615 | /* increment result_len */ 616 | result_len += i + j; 617 | } 618 | } 619 | } 620 | 621 | buffer[result_len] = '\0'; 622 | return result_len; 623 | } 624 | 625 | /* encode many (variadic) */ 626 | size_t 627 | hashids_encode_v(hashids_t *hashids, char *buffer, 628 | size_t numbers_count, ...) 629 | { 630 | size_t i, result; 631 | unsigned long long *numbers; 632 | va_list ap; 633 | 634 | numbers = (unsigned long long *)_hashids_alloc(numbers_count * 635 | sizeof(unsigned long long)); 636 | 637 | if (HASHIDS_UNLIKELY(!numbers)) { 638 | hashids_errno = HASHIDS_ERROR_ALLOC; 639 | return 0; 640 | } 641 | 642 | va_start(ap, numbers_count); 643 | for (i = 0; i < numbers_count; ++i) { 644 | numbers[i] = va_arg(ap, unsigned long long); 645 | } 646 | va_end(ap); 647 | 648 | result = hashids_encode(hashids, buffer, numbers_count, numbers); 649 | _hashids_free(numbers); 650 | 651 | return result; 652 | } 653 | 654 | /* encode one */ 655 | size_t 656 | hashids_encode_one(hashids_t *hashids, char *buffer, 657 | unsigned long long number) 658 | { 659 | return hashids_encode(hashids, buffer, 1, &number); 660 | } 661 | 662 | /* numbers count */ 663 | size_t 664 | hashids_numbers_count(hashids_t *hashids, const char *str) 665 | { 666 | size_t numbers_count; 667 | char ch; 668 | const char *p; 669 | 670 | /* skip characters until we find a guard */ 671 | if (hashids->min_hash_length) { 672 | p = str; 673 | while ((ch = *p)) { 674 | if (strchr(hashids->guards, ch)) { 675 | str = p + 1; 676 | break; 677 | } 678 | 679 | p++; 680 | } 681 | } 682 | 683 | /* parse */ 684 | numbers_count = 0; 685 | while ((ch = *str)) { 686 | if (strchr(hashids->guards, ch)) { 687 | break; 688 | } 689 | if (strchr(hashids->separators, ch)) { 690 | numbers_count++; 691 | str++; 692 | continue; 693 | } 694 | if (!strchr(hashids->alphabet, ch)) { 695 | hashids_errno = HASHIDS_ERROR_INVALID_HASH; 696 | return 0; 697 | } 698 | 699 | str++; 700 | } 701 | 702 | /* account for the last number */ 703 | return numbers_count + 1; 704 | } 705 | 706 | /* decode */ 707 | size_t 708 | hashids_decode(hashids_t *hashids, const char *str, 709 | unsigned long long *numbers, size_t numbers_max) 710 | { 711 | size_t numbers_count; 712 | unsigned long long number; 713 | char lottery, ch, *p, *c; 714 | int p_max; 715 | 716 | if (!numbers || !numbers_max) { 717 | return hashids_numbers_count(hashids, str); 718 | } 719 | 720 | /* skip characters until we find a guard */ 721 | if (hashids->min_hash_length) { 722 | p = (char *)str; 723 | while ((ch = *p)) { 724 | if (strchr(hashids->guards, ch)) { 725 | str = p + 1; 726 | break; 727 | } 728 | 729 | p++; 730 | } 731 | } 732 | 733 | /* get the lottery character */ 734 | lottery = *str++; 735 | 736 | /* copy the alphabet into internal buffer 1 */ 737 | strncpy(hashids->alphabet_copy_1, hashids->alphabet, 738 | hashids->alphabet_length); 739 | 740 | /* alphabet-like buffer used for salt at each iteration */ 741 | hashids->alphabet_copy_2[0] = lottery; 742 | hashids->alphabet_copy_2[1] = '\0'; 743 | strncat(hashids->alphabet_copy_2, hashids->salt, 744 | hashids->alphabet_length - 1); 745 | p = hashids->alphabet_copy_2 + hashids->salt_length + 1; 746 | p_max = hashids->alphabet_length - 1 - hashids->salt_length; 747 | if (p_max > 0) { 748 | strncat(hashids->alphabet_copy_2, hashids->alphabet, 749 | p_max); 750 | } else { 751 | hashids->alphabet_copy_2[hashids->alphabet_length] = '\0'; 752 | } 753 | 754 | /* first shuffle */ 755 | hashids_shuffle(hashids->alphabet_copy_1, hashids->alphabet_length, 756 | hashids->alphabet_copy_2, hashids->alphabet_length); 757 | 758 | /* parse */ 759 | numbers_count = 0; 760 | number = 0; 761 | while ((ch = *str)) { 762 | if (strchr(hashids->guards, ch)) { 763 | break; 764 | } 765 | if (strchr(hashids->separators, ch)) { 766 | /* store the number */ 767 | *numbers++ = number; 768 | 769 | /* check limit */ 770 | if (++numbers_count >= numbers_max) { 771 | return numbers_count; 772 | } 773 | 774 | number = 0; 775 | 776 | /* resalt the alphabet */ 777 | if (p_max > 0) { 778 | strncpy(p, hashids->alphabet_copy_1, p_max); 779 | } 780 | hashids_shuffle(hashids->alphabet_copy_1, hashids->alphabet_length, 781 | hashids->alphabet_copy_2, hashids->alphabet_length); 782 | 783 | str++; 784 | continue; 785 | } 786 | if (!(c = strchr(hashids->alphabet_copy_1, ch))) { 787 | hashids_errno = HASHIDS_ERROR_INVALID_HASH; 788 | return 0; 789 | } 790 | 791 | number *= hashids->alphabet_length; 792 | number += c - hashids->alphabet_copy_1; 793 | 794 | str++; 795 | } 796 | 797 | /* store last number */ 798 | *numbers = number; 799 | 800 | return numbers_count + 1; 801 | } 802 | 803 | /* unsafe decode */ 804 | size_t 805 | hashids_decode_unsafe(hashids_t *hashids, const char *str, 806 | unsigned long long *numbers) 807 | { 808 | return hashids_decode(hashids, str, numbers, (size_t)-1); 809 | } 810 | 811 | /* safe decode */ 812 | size_t 813 | hashids_decode_safe(hashids_t *hashids, const char *str, 814 | unsigned long long *numbers, size_t numbers_max) 815 | { 816 | size_t numbers_count; 817 | size_t len; 818 | char *p; 819 | 820 | numbers_count = hashids_decode(hashids, str, numbers, numbers_max); 821 | if (HASHIDS_UNLIKELY(!numbers_count)) { 822 | hashids_errno = HASHIDS_ERROR_INVALID_HASH; 823 | return 0; 824 | } 825 | 826 | len = hashids_estimate_encoded_size(hashids, numbers_count, numbers); 827 | 828 | p = (char *)_hashids_alloc(len); 829 | if (HASHIDS_UNLIKELY(!p)) { 830 | hashids_errno = HASHIDS_ERROR_ALLOC; 831 | return 0; 832 | } 833 | 834 | len = hashids_encode(hashids, p, numbers_count, numbers); 835 | if (HASHIDS_UNLIKELY(!len)) { 836 | _hashids_free(p); 837 | return 0; 838 | } 839 | 840 | if (strcmp(str, p) != 0) { 841 | _hashids_free(p); 842 | hashids_errno = HASHIDS_ERROR_INVALID_HASH; 843 | return 0; 844 | } 845 | 846 | _hashids_free(p); 847 | return numbers_count; 848 | } 849 | 850 | /* encode hex */ 851 | size_t 852 | hashids_encode_hex(hashids_t *hashids, char *buffer, 853 | const char *hex_str) 854 | { 855 | int len; 856 | char *temp, *p; 857 | size_t result; 858 | unsigned long long number; 859 | 860 | len = strlen(hex_str); 861 | temp = (char *)_hashids_alloc(len + 2); 862 | 863 | if (!temp) { 864 | hashids_errno = HASHIDS_ERROR_ALLOC; 865 | return 0; 866 | } 867 | 868 | temp[0] = '1'; 869 | strncpy(temp + 1, hex_str, len); 870 | 871 | number = strtoull(temp, &p, 16); 872 | 873 | if (p == temp) { 874 | _hashids_free(temp); 875 | hashids_errno = HASHIDS_ERROR_INVALID_NUMBER; 876 | return 0; 877 | } 878 | 879 | result = hashids_encode(hashids, buffer, 1, &number); 880 | _hashids_free(temp); 881 | 882 | return result; 883 | } 884 | 885 | /* decode hex */ 886 | size_t 887 | hashids_decode_hex(hashids_t *hashids, char *str, char *output) 888 | { 889 | size_t result, i; 890 | unsigned long long number; 891 | char ch, *temp; 892 | 893 | result = hashids_numbers_count(hashids, str); 894 | 895 | if (result != 1) { 896 | return 0; 897 | } 898 | 899 | result = hashids_decode_unsafe(hashids, str, &number); 900 | 901 | if (result != 1) { 902 | return 0; 903 | } 904 | 905 | temp = output; 906 | 907 | do { 908 | ch = number % 16; 909 | if (ch > 9) { 910 | ch += 'A' - 10; 911 | } else { 912 | ch += '0'; 913 | } 914 | 915 | *temp++ = (char)ch; 916 | 917 | number /= 16; 918 | } while (number); 919 | 920 | temp--; 921 | *temp = 0; 922 | 923 | for (i = 0; i < (size_t)((temp - output) / 2); ++i) { 924 | ch = *(output + i); 925 | *(output + i) = *(temp - 1 - i); 926 | *(temp - 1 - i) = ch; 927 | } 928 | 929 | return 1; 930 | } 931 | -------------------------------------------------------------------------------- /hashids.h: -------------------------------------------------------------------------------- 1 | #ifndef HASHIDS_H 2 | #define HASHIDS_H 1 3 | 4 | #include 5 | 6 | /* version constants */ 7 | #define HASHIDS_VERSION "1.2.1" 8 | #define HASHIDS_VERSION_MAJOR 1 9 | #define HASHIDS_VERSION_MINOR 2 10 | #define HASHIDS_VERSION_PATCH 1 11 | 12 | /* minimal alphabet length */ 13 | #define HASHIDS_MIN_ALPHABET_LENGTH 16u 14 | 15 | /* separator divisor */ 16 | #define HASHIDS_SEPARATOR_DIVISOR 3.5f 17 | 18 | /* guard divisor */ 19 | #define HASHIDS_GUARD_DIVISOR 12u 20 | 21 | /* default salt */ 22 | #define HASHIDS_DEFAULT_SALT "" 23 | 24 | /* default minimal hash length */ 25 | #define HASHIDS_DEFAULT_MIN_HASH_LENGTH 0u 26 | 27 | /* default alphabet */ 28 | #define HASHIDS_DEFAULT_ALPHABET "abcdefghijklmnopqrstuvwxyz" \ 29 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ 30 | "1234567890" 31 | 32 | /* default separators */ 33 | #define HASHIDS_DEFAULT_SEPARATORS "cfhistuCFHISTU" 34 | 35 | /* error codes */ 36 | #define HASHIDS_ERROR_OK 0 37 | #define HASHIDS_ERROR_ALLOC -1 38 | #define HASHIDS_ERROR_ALPHABET_LENGTH -2 39 | #define HASHIDS_ERROR_ALPHABET_SPACE -3 40 | #define HASHIDS_ERROR_INVALID_HASH -4 41 | #define HASHIDS_ERROR_INVALID_NUMBER -5 42 | 43 | /* thread-safe hashids_errno indirection */ 44 | extern int *__hashids_errno_addr(void); 45 | #define hashids_errno (*__hashids_errno_addr()) 46 | 47 | /* alloc & free */ 48 | extern void *(*_hashids_alloc)(size_t size); 49 | extern void (*_hashids_free)(void *ptr); 50 | 51 | /* the hashids "object" */ 52 | struct hashids_s { 53 | char *alphabet; 54 | char *alphabet_copy_1; 55 | char *alphabet_copy_2; 56 | size_t alphabet_length; 57 | 58 | char *salt; 59 | size_t salt_length; 60 | 61 | char *separators; 62 | size_t separators_count; 63 | 64 | char *guards; 65 | size_t guards_count; 66 | 67 | size_t min_hash_length; 68 | }; 69 | typedef struct hashids_s hashids_t; 70 | 71 | /* exported function definitions */ 72 | void 73 | hashids_shuffle(char *str, size_t str_length, char *salt, size_t salt_length); 74 | 75 | void 76 | hashids_free(hashids_t *hashids); 77 | 78 | hashids_t * 79 | hashids_init3(const char *salt, size_t min_hash_length, 80 | const char *alphabet); 81 | 82 | hashids_t * 83 | hashids_init2(const char *salt, size_t min_hash_length); 84 | 85 | hashids_t * 86 | hashids_init(const char *salt); 87 | 88 | size_t 89 | hashids_estimate_encoded_size(hashids_t *hashids, size_t numbers_count, 90 | unsigned long long *numbers); 91 | 92 | size_t 93 | hashids_estimate_encoded_size_v(hashids_t *hashids, size_t numbers_count, ...); 94 | 95 | size_t 96 | hashids_encode(hashids_t *hashids, char *buffer, size_t numbers_count, 97 | unsigned long long *numbers); 98 | 99 | size_t 100 | hashids_encode_v(hashids_t *hashids, char *buffer, size_t numbers_count, ...); 101 | 102 | size_t 103 | hashids_encode_one(hashids_t *hashids, char *buffer, 104 | unsigned long long number); 105 | 106 | size_t 107 | hashids_numbers_count(hashids_t *hashids, const char *str); 108 | 109 | size_t 110 | hashids_decode(hashids_t *hashids, const char *str, 111 | unsigned long long *numbers, size_t numbers_max); 112 | 113 | size_t 114 | hashids_decode_unsafe(hashids_t *hashids, const char *str, 115 | unsigned long long *numbers); 116 | 117 | size_t 118 | hashids_decode_safe(hashids_t *hashids, const char *str, 119 | unsigned long long *numbers, size_t numbers_max); 120 | 121 | size_t 122 | hashids_encode_hex(hashids_t *hashids, char *buffer, const char *hex_str); 123 | 124 | size_t 125 | hashids_decode_hex(hashids_t *hashids, char *str, char *output); 126 | 127 | #endif 128 | -------------------------------------------------------------------------------- /pg_hashids--1.0--1.1.sql: -------------------------------------------------------------------------------- 1 | \echo Use "CREATE EXTENSION pg_hashids" to load this file. \quit 2 | 3 | -- v1 4 | CREATE OR REPLACE FUNCTION hash_encode(BIGINT) RETURNS TEXT 5 | AS 'pg_hashids', 'id_encode' LANGUAGE C; 6 | CREATE OR REPLACE FUNCTION hash_encode(BIGINT, TEXT) RETURNS TEXT 7 | AS 'pg_hashids', 'id_encode' LANGUAGE C; 8 | CREATE OR REPLACE FUNCTION hash_encode(BIGINT, TEXT, INT) RETURNS TEXT 9 | AS 'pg_hashids', 'id_encode' LANGUAGE C; 10 | CREATE OR REPLACE FUNCTION hash_decode(TEXT, TEXT, INT) RETURNS INT 11 | AS 'pg_hashids', 'id_decode_once' LANGUAGE C; 12 | 13 | -- v1.1 14 | CREATE OR REPLACE FUNCTION id_encode(BIGINT) RETURNS TEXT 15 | AS 'pg_hashids', 'id_encode' LANGUAGE C; 16 | CREATE OR REPLACE FUNCTION id_encode(BIGINT, TEXT) RETURNS TEXT 17 | AS 'pg_hashids', 'id_encode' LANGUAGE C; 18 | CREATE OR REPLACE FUNCTION id_encode(BIGINT, TEXT, INT) RETURNS TEXT 19 | AS 'pg_hashids', 'id_encode' LANGUAGE C; 20 | CREATE OR REPLACE FUNCTION id_encode(BIGINT, TEXT, INT, TEXT) RETURNS TEXT 21 | AS 'pg_hashids', 'id_encode' LANGUAGE C; 22 | 23 | CREATE OR REPLACE FUNCTION id_encode(BIGINT[]) RETURNS TEXT 24 | AS 'pg_hashids', 'id_encode_array' LANGUAGE C; 25 | CREATE OR REPLACE FUNCTION id_encode(BIGINT[], TEXT) RETURNS TEXT 26 | AS 'pg_hashids', 'id_encode_array' LANGUAGE C; 27 | CREATE OR REPLACE FUNCTION id_encode(BIGINT[], TEXT, INT) RETURNS TEXT 28 | AS 'pg_hashids', 'id_encode_array' LANGUAGE C; 29 | CREATE OR REPLACE FUNCTION id_encode(BIGINT[], TEXT, INT, TEXT) RETURNS TEXT 30 | AS 'pg_hashids', 'id_encode_array' LANGUAGE C; 31 | 32 | CREATE OR REPLACE FUNCTION id_decode(TEXT) RETURNS BIGINT[] 33 | AS 'pg_hashids', 'id_decode' LANGUAGE C; 34 | CREATE OR REPLACE FUNCTION id_decode(TEXT, TEXT) RETURNS BIGINT[] 35 | AS 'pg_hashids', 'id_decode' LANGUAGE C; 36 | CREATE OR REPLACE FUNCTION id_decode(TEXT, TEXT, INT) RETURNS BIGINT[] 37 | AS 'pg_hashids', 'id_decode' LANGUAGE C; 38 | CREATE OR REPLACE FUNCTION id_decode(TEXT, TEXT, INT, TEXT) RETURNS BIGINT[] 39 | AS 'pg_hashids', 'id_decode' LANGUAGE C; 40 | 41 | CREATE OR REPLACE FUNCTION id_decode_once(TEXT) RETURNS BIGINT 42 | AS 'pg_hashids', 'id_decode_once' LANGUAGE C; 43 | CREATE OR REPLACE FUNCTION id_decode_once(TEXT, TEXT) RETURNS BIGINT 44 | AS 'pg_hashids', 'id_decode_once' LANGUAGE C; 45 | CREATE OR REPLACE FUNCTION id_decode_once(TEXT, TEXT, INT) RETURNS BIGINT 46 | AS 'pg_hashids', 'id_decode_once' LANGUAGE C; 47 | CREATE OR REPLACE FUNCTION id_decode_once(TEXT, TEXT, INT, TEXT) RETURNS BIGINT 48 | AS 'pg_hashids', 'id_decode_once' LANGUAGE C; 49 | -------------------------------------------------------------------------------- /pg_hashids--1.1--1.2.sql: -------------------------------------------------------------------------------- 1 | -- use CREATE EXTENSION 2 | \echo Use "CREATE EXTENSION pg_hashids" to load this file. \quit 3 | 4 | -- v1 5 | CREATE OR REPLACE FUNCTION hash_encode(BIGINT) RETURNS TEXT 6 | AS 'pg_hashids', 'id_encode' LANGUAGE C; 7 | CREATE OR REPLACE FUNCTION hash_encode(BIGINT, TEXT) RETURNS TEXT 8 | AS 'pg_hashids', 'id_encode' LANGUAGE C; 9 | CREATE OR REPLACE FUNCTION hash_encode(BIGINT, TEXT, INT) RETURNS TEXT 10 | AS 'pg_hashids', 'id_encode' LANGUAGE C; 11 | CREATE OR REPLACE FUNCTION hash_decode(TEXT, TEXT, INT) RETURNS INT 12 | AS 'pg_hashids', 'id_decode_once' LANGUAGE C; 13 | 14 | -- v1.2 15 | CREATE OR REPLACE FUNCTION id_encode(BIGINT) RETURNS TEXT 16 | AS 'pg_hashids', 'id_encode' LANGUAGE C; 17 | CREATE OR REPLACE FUNCTION id_encode(BIGINT, TEXT) RETURNS TEXT 18 | AS 'pg_hashids', 'id_encode' LANGUAGE C; 19 | CREATE OR REPLACE FUNCTION id_encode(BIGINT, TEXT, INT) RETURNS TEXT 20 | AS 'pg_hashids', 'id_encode' LANGUAGE C; 21 | CREATE OR REPLACE FUNCTION id_encode(BIGINT, TEXT, INT, TEXT) RETURNS TEXT 22 | AS 'pg_hashids', 'id_encode' LANGUAGE C; 23 | 24 | CREATE OR REPLACE FUNCTION id_encode(BIGINT[]) RETURNS TEXT 25 | AS 'pg_hashids', 'id_encode_array' LANGUAGE C; 26 | CREATE OR REPLACE FUNCTION id_encode(BIGINT[], TEXT) RETURNS TEXT 27 | AS 'pg_hashids', 'id_encode_array' LANGUAGE C; 28 | CREATE OR REPLACE FUNCTION id_encode(BIGINT[], TEXT, INT) RETURNS TEXT 29 | AS 'pg_hashids', 'id_encode_array' LANGUAGE C; 30 | CREATE OR REPLACE FUNCTION id_encode(BIGINT[], TEXT, INT, TEXT) RETURNS TEXT 31 | AS 'pg_hashids', 'id_encode_array' LANGUAGE C; 32 | 33 | CREATE OR REPLACE FUNCTION id_decode(TEXT) RETURNS BIGINT[] 34 | AS 'pg_hashids', 'id_decode' LANGUAGE C; 35 | CREATE OR REPLACE FUNCTION id_decode(TEXT, TEXT) RETURNS BIGINT[] 36 | AS 'pg_hashids', 'id_decode' LANGUAGE C; 37 | CREATE OR REPLACE FUNCTION id_decode(TEXT, TEXT, INT) RETURNS BIGINT[] 38 | AS 'pg_hashids', 'id_decode' LANGUAGE C; 39 | CREATE OR REPLACE FUNCTION id_decode(TEXT, TEXT, INT, TEXT) RETURNS BIGINT[] 40 | AS 'pg_hashids', 'id_decode' LANGUAGE C; 41 | 42 | CREATE OR REPLACE FUNCTION id_decode_once(TEXT) RETURNS BIGINT 43 | AS 'pg_hashids', 'id_decode_once' LANGUAGE C; 44 | CREATE OR REPLACE FUNCTION id_decode_once(TEXT, TEXT) RETURNS BIGINT 45 | AS 'pg_hashids', 'id_decode_once' LANGUAGE C; 46 | CREATE OR REPLACE FUNCTION id_decode_once(TEXT, TEXT, INT) RETURNS BIGINT 47 | AS 'pg_hashids', 'id_decode_once' LANGUAGE C; 48 | CREATE OR REPLACE FUNCTION id_decode_once(TEXT, TEXT, INT, TEXT) RETURNS BIGINT 49 | AS 'pg_hashids', 'id_decode_once' LANGUAGE C; 50 | -------------------------------------------------------------------------------- /pg_hashids--1.2--1.3.sql: -------------------------------------------------------------------------------- 1 | \echo Use "CREATE EXTENSION pg_hashids" to load this file. \quit 2 | 3 | -- v1 4 | CREATE OR REPLACE FUNCTION hash_encode(BIGINT) RETURNS TEXT 5 | AS 'MODULE_PATHNAME', 'id_encode' LANGUAGE C IMMUTABLE STRICT; 6 | CREATE OR REPLACE FUNCTION hash_encode(BIGINT, TEXT) RETURNS TEXT 7 | AS 'MODULE_PATHNAME', 'id_encode' LANGUAGE C IMMUTABLE STRICT; 8 | CREATE OR REPLACE FUNCTION hash_encode(BIGINT, TEXT, INT) RETURNS TEXT 9 | AS 'MODULE_PATHNAME', 'id_encode' LANGUAGE C IMMUTABLE STRICT; 10 | CREATE OR REPLACE FUNCTION hash_decode(TEXT, TEXT, INT) RETURNS INT 11 | AS 'MODULE_PATHNAME', 'id_decode_once' LANGUAGE C IMMUTABLE STRICT; 12 | 13 | -- v1.3 14 | CREATE OR REPLACE FUNCTION id_encode(BIGINT) RETURNS TEXT 15 | AS 'MODULE_PATHNAME', 'id_encode' LANGUAGE C IMMUTABLE STRICT; 16 | CREATE OR REPLACE FUNCTION id_encode(BIGINT, TEXT) RETURNS TEXT 17 | AS 'MODULE_PATHNAME', 'id_encode' LANGUAGE C IMMUTABLE STRICT; 18 | CREATE OR REPLACE FUNCTION id_encode(BIGINT, TEXT, INT) RETURNS TEXT 19 | AS 'MODULE_PATHNAME', 'id_encode' LANGUAGE C IMMUTABLE STRICT; 20 | CREATE OR REPLACE FUNCTION id_encode(BIGINT, TEXT, INT, TEXT) RETURNS TEXT 21 | AS 'MODULE_PATHNAME', 'id_encode' LANGUAGE C IMMUTABLE STRICT; 22 | 23 | CREATE OR REPLACE FUNCTION id_encode(BIGINT[]) RETURNS TEXT 24 | AS 'MODULE_PATHNAME', 'id_encode_array' LANGUAGE C IMMUTABLE STRICT; 25 | CREATE OR REPLACE FUNCTION id_encode(BIGINT[], TEXT) RETURNS TEXT 26 | AS 'MODULE_PATHNAME', 'id_encode_array' LANGUAGE C IMMUTABLE STRICT; 27 | CREATE OR REPLACE FUNCTION id_encode(BIGINT[], TEXT, INT) RETURNS TEXT 28 | AS 'MODULE_PATHNAME', 'id_encode_array' LANGUAGE C IMMUTABLE STRICT; 29 | CREATE OR REPLACE FUNCTION id_encode(BIGINT[], TEXT, INT, TEXT) RETURNS TEXT 30 | AS 'MODULE_PATHNAME', 'id_encode_array' LANGUAGE C IMMUTABLE STRICT; 31 | 32 | CREATE OR REPLACE FUNCTION id_decode(TEXT) RETURNS BIGINT[] 33 | AS 'MODULE_PATHNAME', 'id_decode' LANGUAGE C IMMUTABLE STRICT; 34 | CREATE OR REPLACE FUNCTION id_decode(TEXT, TEXT) RETURNS BIGINT[] 35 | AS 'MODULE_PATHNAME', 'id_decode' LANGUAGE C IMMUTABLE STRICT; 36 | CREATE OR REPLACE FUNCTION id_decode(TEXT, TEXT, INT) RETURNS BIGINT[] 37 | AS 'MODULE_PATHNAME', 'id_decode' LANGUAGE C IMMUTABLE STRICT; 38 | CREATE OR REPLACE FUNCTION id_decode(TEXT, TEXT, INT, TEXT) RETURNS BIGINT[] 39 | AS 'MODULE_PATHNAME', 'id_decode' LANGUAGE C IMMUTABLE STRICT; 40 | 41 | CREATE OR REPLACE FUNCTION id_decode_once(TEXT) RETURNS BIGINT 42 | AS 'MODULE_PATHNAME', 'id_decode_once' LANGUAGE C IMMUTABLE STRICT; 43 | CREATE OR REPLACE FUNCTION id_decode_once(TEXT, TEXT) RETURNS BIGINT 44 | AS 'MODULE_PATHNAME', 'id_decode_once' LANGUAGE C IMMUTABLE STRICT; 45 | CREATE OR REPLACE FUNCTION id_decode_once(TEXT, TEXT, INT) RETURNS BIGINT 46 | AS 'MODULE_PATHNAME', 'id_decode_once' LANGUAGE C IMMUTABLE STRICT; 47 | CREATE OR REPLACE FUNCTION id_decode_once(TEXT, TEXT, INT, TEXT) RETURNS BIGINT 48 | AS 'MODULE_PATHNAME', 'id_decode_once' LANGUAGE C IMMUTABLE STRICT; 49 | -------------------------------------------------------------------------------- /pg_hashids--1.2.1--1.3.sql: -------------------------------------------------------------------------------- 1 | \echo Use "CREATE EXTENSION pg_hashids" to load this file. \quit 2 | 3 | -- v1 4 | CREATE OR REPLACE FUNCTION hash_encode(BIGINT) RETURNS TEXT 5 | AS 'MODULE_PATHNAME', 'id_encode' LANGUAGE C IMMUTABLE STRICT; 6 | CREATE OR REPLACE FUNCTION hash_encode(BIGINT, TEXT) RETURNS TEXT 7 | AS 'MODULE_PATHNAME', 'id_encode' LANGUAGE C IMMUTABLE STRICT; 8 | CREATE OR REPLACE FUNCTION hash_encode(BIGINT, TEXT, INT) RETURNS TEXT 9 | AS 'MODULE_PATHNAME', 'id_encode' LANGUAGE C IMMUTABLE STRICT; 10 | CREATE OR REPLACE FUNCTION hash_decode(TEXT, TEXT, INT) RETURNS INT 11 | AS 'MODULE_PATHNAME', 'id_decode_once' LANGUAGE C IMMUTABLE STRICT; 12 | 13 | -- v1.3 14 | CREATE OR REPLACE FUNCTION id_encode(BIGINT) RETURNS TEXT 15 | AS 'MODULE_PATHNAME', 'id_encode' LANGUAGE C IMMUTABLE STRICT; 16 | CREATE OR REPLACE FUNCTION id_encode(BIGINT, TEXT) RETURNS TEXT 17 | AS 'MODULE_PATHNAME', 'id_encode' LANGUAGE C IMMUTABLE STRICT; 18 | CREATE OR REPLACE FUNCTION id_encode(BIGINT, TEXT, INT) RETURNS TEXT 19 | AS 'MODULE_PATHNAME', 'id_encode' LANGUAGE C IMMUTABLE STRICT; 20 | CREATE OR REPLACE FUNCTION id_encode(BIGINT, TEXT, INT, TEXT) RETURNS TEXT 21 | AS 'MODULE_PATHNAME', 'id_encode' LANGUAGE C IMMUTABLE STRICT; 22 | 23 | CREATE OR REPLACE FUNCTION id_encode(BIGINT[]) RETURNS TEXT 24 | AS 'MODULE_PATHNAME', 'id_encode_array' LANGUAGE C IMMUTABLE STRICT; 25 | CREATE OR REPLACE FUNCTION id_encode(BIGINT[], TEXT) RETURNS TEXT 26 | AS 'MODULE_PATHNAME', 'id_encode_array' LANGUAGE C IMMUTABLE STRICT; 27 | CREATE OR REPLACE FUNCTION id_encode(BIGINT[], TEXT, INT) RETURNS TEXT 28 | AS 'MODULE_PATHNAME', 'id_encode_array' LANGUAGE C IMMUTABLE STRICT; 29 | CREATE OR REPLACE FUNCTION id_encode(BIGINT[], TEXT, INT, TEXT) RETURNS TEXT 30 | AS 'MODULE_PATHNAME', 'id_encode_array' LANGUAGE C IMMUTABLE STRICT; 31 | 32 | CREATE OR REPLACE FUNCTION id_decode(TEXT) RETURNS BIGINT[] 33 | AS 'MODULE_PATHNAME', 'id_decode' LANGUAGE C IMMUTABLE STRICT; 34 | CREATE OR REPLACE FUNCTION id_decode(TEXT, TEXT) RETURNS BIGINT[] 35 | AS 'MODULE_PATHNAME', 'id_decode' LANGUAGE C IMMUTABLE STRICT; 36 | CREATE OR REPLACE FUNCTION id_decode(TEXT, TEXT, INT) RETURNS BIGINT[] 37 | AS 'MODULE_PATHNAME', 'id_decode' LANGUAGE C IMMUTABLE STRICT; 38 | CREATE OR REPLACE FUNCTION id_decode(TEXT, TEXT, INT, TEXT) RETURNS BIGINT[] 39 | AS 'MODULE_PATHNAME', 'id_decode' LANGUAGE C IMMUTABLE STRICT; 40 | 41 | CREATE OR REPLACE FUNCTION id_decode_once(TEXT) RETURNS BIGINT 42 | AS 'MODULE_PATHNAME', 'id_decode_once' LANGUAGE C IMMUTABLE STRICT; 43 | CREATE OR REPLACE FUNCTION id_decode_once(TEXT, TEXT) RETURNS BIGINT 44 | AS 'MODULE_PATHNAME', 'id_decode_once' LANGUAGE C IMMUTABLE STRICT; 45 | CREATE OR REPLACE FUNCTION id_decode_once(TEXT, TEXT, INT) RETURNS BIGINT 46 | AS 'MODULE_PATHNAME', 'id_decode_once' LANGUAGE C IMMUTABLE STRICT; 47 | CREATE OR REPLACE FUNCTION id_decode_once(TEXT, TEXT, INT, TEXT) RETURNS BIGINT 48 | AS 'MODULE_PATHNAME', 'id_decode_once' LANGUAGE C IMMUTABLE STRICT; 49 | -------------------------------------------------------------------------------- /pg_hashids--1.3.sql: -------------------------------------------------------------------------------- 1 | \echo Use "CREATE EXTENSION pg_hashids" to load this file. \quit 2 | 3 | -- v1 4 | CREATE OR REPLACE FUNCTION hash_encode(BIGINT) RETURNS TEXT 5 | AS 'MODULE_PATHNAME', 'id_encode' LANGUAGE C IMMUTABLE STRICT; 6 | CREATE OR REPLACE FUNCTION hash_encode(BIGINT, TEXT) RETURNS TEXT 7 | AS 'MODULE_PATHNAME', 'id_encode' LANGUAGE C IMMUTABLE STRICT; 8 | CREATE OR REPLACE FUNCTION hash_encode(BIGINT, TEXT, INT) RETURNS TEXT 9 | AS 'MODULE_PATHNAME', 'id_encode' LANGUAGE C IMMUTABLE STRICT; 10 | CREATE OR REPLACE FUNCTION hash_decode(TEXT, TEXT, INT) RETURNS INT 11 | AS 'MODULE_PATHNAME', 'id_decode_once' LANGUAGE C IMMUTABLE STRICT; 12 | 13 | -- v1.3 14 | CREATE OR REPLACE FUNCTION id_encode(BIGINT) RETURNS TEXT 15 | AS 'MODULE_PATHNAME', 'id_encode' LANGUAGE C IMMUTABLE STRICT; 16 | CREATE OR REPLACE FUNCTION id_encode(BIGINT, TEXT) RETURNS TEXT 17 | AS 'MODULE_PATHNAME', 'id_encode' LANGUAGE C IMMUTABLE STRICT; 18 | CREATE OR REPLACE FUNCTION id_encode(BIGINT, TEXT, INT) RETURNS TEXT 19 | AS 'MODULE_PATHNAME', 'id_encode' LANGUAGE C IMMUTABLE STRICT; 20 | CREATE OR REPLACE FUNCTION id_encode(BIGINT, TEXT, INT, TEXT) RETURNS TEXT 21 | AS 'MODULE_PATHNAME', 'id_encode' LANGUAGE C IMMUTABLE STRICT; 22 | 23 | CREATE OR REPLACE FUNCTION id_encode(BIGINT[]) RETURNS TEXT 24 | AS 'MODULE_PATHNAME', 'id_encode_array' LANGUAGE C IMMUTABLE STRICT; 25 | CREATE OR REPLACE FUNCTION id_encode(BIGINT[], TEXT) RETURNS TEXT 26 | AS 'MODULE_PATHNAME', 'id_encode_array' LANGUAGE C IMMUTABLE STRICT; 27 | CREATE OR REPLACE FUNCTION id_encode(BIGINT[], TEXT, INT) RETURNS TEXT 28 | AS 'MODULE_PATHNAME', 'id_encode_array' LANGUAGE C IMMUTABLE STRICT; 29 | CREATE OR REPLACE FUNCTION id_encode(BIGINT[], TEXT, INT, TEXT) RETURNS TEXT 30 | AS 'MODULE_PATHNAME', 'id_encode_array' LANGUAGE C IMMUTABLE STRICT; 31 | 32 | CREATE OR REPLACE FUNCTION id_decode(TEXT) RETURNS BIGINT[] 33 | AS 'MODULE_PATHNAME', 'id_decode' LANGUAGE C IMMUTABLE STRICT; 34 | CREATE OR REPLACE FUNCTION id_decode(TEXT, TEXT) RETURNS BIGINT[] 35 | AS 'MODULE_PATHNAME', 'id_decode' LANGUAGE C IMMUTABLE STRICT; 36 | CREATE OR REPLACE FUNCTION id_decode(TEXT, TEXT, INT) RETURNS BIGINT[] 37 | AS 'MODULE_PATHNAME', 'id_decode' LANGUAGE C IMMUTABLE STRICT; 38 | CREATE OR REPLACE FUNCTION id_decode(TEXT, TEXT, INT, TEXT) RETURNS BIGINT[] 39 | AS 'MODULE_PATHNAME', 'id_decode' LANGUAGE C IMMUTABLE STRICT; 40 | 41 | CREATE OR REPLACE FUNCTION id_decode_once(TEXT) RETURNS BIGINT 42 | AS 'MODULE_PATHNAME', 'id_decode_once' LANGUAGE C IMMUTABLE STRICT; 43 | CREATE OR REPLACE FUNCTION id_decode_once(TEXT, TEXT) RETURNS BIGINT 44 | AS 'MODULE_PATHNAME', 'id_decode_once' LANGUAGE C IMMUTABLE STRICT; 45 | CREATE OR REPLACE FUNCTION id_decode_once(TEXT, TEXT, INT) RETURNS BIGINT 46 | AS 'MODULE_PATHNAME', 'id_decode_once' LANGUAGE C IMMUTABLE STRICT; 47 | CREATE OR REPLACE FUNCTION id_decode_once(TEXT, TEXT, INT, TEXT) RETURNS BIGINT 48 | AS 'MODULE_PATHNAME', 'id_decode_once' LANGUAGE C IMMUTABLE STRICT; 49 | -------------------------------------------------------------------------------- /pg_hashids.c: -------------------------------------------------------------------------------- 1 | #include "postgres.h" 2 | #include "fmgr.h" 3 | 4 | #include "funcapi.h" 5 | #include "catalog/pg_type.h" 6 | #include "utils/array.h" 7 | #include "utils/builtins.h" 8 | 9 | #include "hashids.h" 10 | 11 | #ifdef PG_MODULE_MAGIC 12 | PG_MODULE_MAGIC; 13 | #endif 14 | 15 | PG_FUNCTION_INFO_V1(id_encode); 16 | PG_FUNCTION_INFO_V1(id_encode_array); 17 | PG_FUNCTION_INFO_V1(id_decode); 18 | PG_FUNCTION_INFO_V1(id_decode_once); 19 | 20 | static void hashids_error() 21 | { 22 | switch (hashids_errno) { 23 | case HASHIDS_ERROR_ALLOC: 24 | ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("allocation failed"))); 25 | break; 26 | case HASHIDS_ERROR_ALPHABET_LENGTH: 27 | ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("alphabet is too short"))); 28 | break; 29 | case HASHIDS_ERROR_ALPHABET_SPACE: 30 | ereport(ERROR, 31 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), 32 | errmsg("alphabet contains whitespace characters"))); 33 | break; 34 | case HASHIDS_ERROR_INVALID_HASH: 35 | ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid hash"))); 36 | break; 37 | default: 38 | ereport(ERROR, (errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION), errmsg("unknown error"))); 39 | break; 40 | } 41 | } 42 | 43 | // based on new_intArrayType contrib/intarray/_int_tool.c 44 | static ArrayType *new_intArrayType(int num) 45 | { 46 | ArrayType *r; 47 | int nbytes = ARR_OVERHEAD_NONULLS(1) + sizeof(int64) * num; 48 | 49 | r = (ArrayType *) palloc0(nbytes); 50 | 51 | SET_VARSIZE(r, nbytes); 52 | ARR_NDIM(r) = 1; 53 | r->dataoffset = 0; /* marker for no null bitmap */ 54 | ARR_ELEMTYPE(r) = INT8OID; 55 | ARR_DIMS(r)[0] = num; 56 | ARR_LBOUND(r)[0] = 1; 57 | 58 | return r; 59 | } 60 | 61 | Datum 62 | id_encode(PG_FUNCTION_ARGS) 63 | { 64 | // Declaration 65 | int64 number; 66 | text *hash_string; 67 | hashids_t *hashids; 68 | 69 | unsigned int bytes_encoded; 70 | char *hash; 71 | 72 | // Arguments 73 | number = PG_GETARG_INT64(0); 74 | 75 | if (PG_NARGS() == 2) { 76 | hashids = hashids_init2(text_to_cstring(PG_GETARG_TEXT_P(1)), 0); 77 | } else if (PG_NARGS() == 3) { 78 | hashids = hashids_init2(text_to_cstring(PG_GETARG_TEXT_P(1)), PG_GETARG_INT32(2)); 79 | } else if (PG_NARGS() == 4) { 80 | hashids = hashids_init3(text_to_cstring(PG_GETARG_TEXT_P(1)), PG_GETARG_INT32(2), 81 | text_to_cstring(PG_GETARG_TEXT_P(3))); 82 | } else { 83 | hashids = hashids_init(NULL); 84 | } 85 | if (!hashids) { 86 | hashids_error(); 87 | } 88 | 89 | hash = palloc0(hashids_estimate_encoded_size(hashids, 1, (unsigned long long *) &number)); 90 | bytes_encoded = hashids_encode_one(hashids, hash, (unsigned long long) number); 91 | hash_string = cstring_to_text_with_len(hash, bytes_encoded); 92 | 93 | hashids_free(hashids); 94 | pfree(hash); 95 | 96 | PG_RETURN_TEXT_P(hash_string); 97 | } 98 | 99 | Datum 100 | id_encode_array(PG_FUNCTION_ARGS) 101 | { 102 | ArrayType *numbers; 103 | size_t numbers_count; 104 | 105 | // Declaration 106 | text *hash_string; 107 | hashids_t *hashids; 108 | 109 | unsigned int bytes_encoded; 110 | char *hash; 111 | 112 | // Arguments 113 | numbers = PG_GETARG_ARRAYTYPE_P(0); 114 | numbers_count = ARR_DIMS(numbers)[0]; 115 | 116 | if (array_contains_nulls(numbers)) { 117 | ereport(ERROR, 118 | (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), 119 | errmsg("null value not allowed for array element"))); 120 | 121 | } 122 | if (PG_NARGS() == 2) { 123 | hashids = hashids_init2(text_to_cstring(PG_GETARG_TEXT_P(1)), 0); 124 | } else if (PG_NARGS() == 3) { 125 | hashids = hashids_init2(text_to_cstring(PG_GETARG_TEXT_P(1)), PG_GETARG_INT32(2)); 126 | } else if (PG_NARGS() == 4) { 127 | hashids = hashids_init3(text_to_cstring(PG_GETARG_TEXT_P(1)), PG_GETARG_INT32(2), 128 | text_to_cstring(PG_GETARG_TEXT_P(3))); 129 | } else { 130 | hashids = hashids_init(NULL); 131 | } 132 | if (!hashids) { 133 | hashids_error(); 134 | } 135 | 136 | hash = palloc0( 137 | hashids_estimate_encoded_size(hashids, numbers_count, (unsigned long long *) ARR_DATA_PTR(numbers))); 138 | bytes_encoded = hashids_encode(hashids, hash, numbers_count, (unsigned long long *) ARR_DATA_PTR(numbers)); 139 | hash_string = cstring_to_text_with_len(hash, bytes_encoded); 140 | 141 | hashids_free(hashids); 142 | pfree(hash); 143 | 144 | PG_RETURN_TEXT_P(hash_string); 145 | } 146 | 147 | Datum 148 | id_decode(PG_FUNCTION_ARGS) 149 | { 150 | // Declaration 151 | hashids_t *hashids; 152 | int64 *numbers, *resultValues; 153 | char *hash; 154 | size_t numbers_count; 155 | ArrayType *resultArray; 156 | 157 | if (PG_NARGS() == 2) { 158 | hashids = hashids_init2(text_to_cstring(PG_GETARG_TEXT_P(1)), 0); 159 | } else if (PG_NARGS() == 3) { 160 | hashids = hashids_init2(text_to_cstring(PG_GETARG_TEXT_P(1)), PG_GETARG_INT32(2)); 161 | } else if (PG_NARGS() == 4) { 162 | hashids = hashids_init3(text_to_cstring(PG_GETARG_TEXT_P(1)), PG_GETARG_INT32(2), 163 | text_to_cstring(PG_GETARG_TEXT_P(3))); 164 | } else { 165 | hashids = hashids_init(NULL); 166 | } 167 | if (!hashids) { 168 | hashids_error(); 169 | } 170 | 171 | hash = text_to_cstring(PG_GETARG_TEXT_P(0)); 172 | numbers_count = hashids_numbers_count(hashids, hash); 173 | if (!numbers_count) { 174 | hashids_error(); 175 | } 176 | 177 | numbers = palloc0(numbers_count * sizeof(int64)); 178 | hashids_decode(hashids, hash, (unsigned long long *) numbers, numbers_count); 179 | 180 | hashids_free(hashids); 181 | pfree(hash); 182 | 183 | resultArray = new_intArrayType(numbers_count); 184 | resultValues = (int64 *) ARR_DATA_PTR(resultArray); 185 | memcpy(resultValues, numbers, numbers_count * sizeof(int64)); 186 | 187 | pfree(numbers); 188 | 189 | PG_RETURN_ARRAYTYPE_P(resultArray); 190 | } 191 | 192 | Datum 193 | id_decode_once(PG_FUNCTION_ARGS) 194 | { 195 | // Declaration 196 | hashids_t *hashids; 197 | int64 number; 198 | char *hash; 199 | size_t numbers_count; 200 | 201 | if (PG_NARGS() == 2) { 202 | hashids = hashids_init2(text_to_cstring(PG_GETARG_TEXT_P(1)), 0); 203 | } else if (PG_NARGS() == 3) { 204 | hashids = hashids_init2(text_to_cstring(PG_GETARG_TEXT_P(1)), PG_GETARG_INT32(2)); 205 | } else if (PG_NARGS() == 4) { 206 | hashids = hashids_init3(text_to_cstring(PG_GETARG_TEXT_P(1)), PG_GETARG_INT32(2), 207 | text_to_cstring(PG_GETARG_TEXT_P(3))); 208 | } else { 209 | hashids = hashids_init(NULL); 210 | } 211 | if (!hashids) { 212 | hashids_error(); 213 | } 214 | 215 | hash = text_to_cstring(PG_GETARG_TEXT_P(0)); 216 | numbers_count = hashids_numbers_count(hashids, hash); 217 | if (!numbers_count) { 218 | hashids_error(); 219 | } 220 | 221 | hashids_decode(hashids, hash, (unsigned long long *) &number, 1); 222 | 223 | hashids_free(hashids); 224 | pfree(hash); 225 | 226 | PG_RETURN_INT64(number); 227 | } 228 | -------------------------------------------------------------------------------- /pg_hashids.control: -------------------------------------------------------------------------------- 1 | default_version = '1.3' 2 | comment = 'pg_hashids' 3 | module_pathname = '$libdir/pg_hashids' 4 | relocatable = true 5 | -------------------------------------------------------------------------------- /sql/pg_hashids.sql: -------------------------------------------------------------------------------- 1 | \set VERBOSITY terse 2 | CREATE EXTENSION pg_hashids; 3 | 4 | -- Encoding tests 5 | SELECT id_encode(1001); -- Result: jNl 6 | SELECT id_encode(1234567, 'This is my salt'); -- Result: Pdzxp 7 | SELECT id_encode(1234567, 'This is my salt', 10); -- Result: PlRPdzxpR7 8 | SELECT id_encode(1234567, 'This is my salt', 10, 'abcdefghijABCDxFGHIJ1234567890'); -- Result: 3GJ956J9B9 9 | 10 | -- Decoding tests 11 | SELECT id_decode('jNl'); -- Result: {1001} 12 | SELECT id_decode('Pdzxp', 'This is my salt'); -- Result: {1234567} 13 | SELECT id_decode('PlRPdzxpR7', 'This is my salt', 10); -- Result: {1234567} 14 | SELECT id_decode('3GJ956J9B9', 'This is my salt', 10, 'abcdefghijABCDxFGHIJ1234567890'); -- Result: {1234567} 15 | 16 | SELECT id_decode_once('jNl'); -- Result: 1001 17 | SELECT id_decode_once('Pdzxp', 'This is my salt'); -- Result: 1234567 18 | SELECT id_decode_once('PlRPdzxpR7', 'This is my salt', 10); -- Result: 1234567 19 | SELECT id_decode_once('3GJ956J9B9', 'This is my salt', 10, 'abcdefghijABCDxFGHIJ1234567890'); -- Result: 1234567 20 | 21 | --------------------------------------------------------------------------------