├── pg_xid.control ├── Makefile ├── pg_xid--1.0.sql ├── xid.h ├── LICENSE ├── pg_xid.c ├── README.md └── xid.c /pg_xid.control: -------------------------------------------------------------------------------- 1 | comment = 'pg_xid' 2 | default_version = '1.0' 3 | 4 | relocatable = true -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | MODULE_big = pg_xid 2 | OBJS = pg_xid.o xid.o 3 | 4 | EXTENSION = pg_xid 5 | DATA = pg_xid--1.0.sql 6 | 7 | PG_CONFIG = pg_config 8 | PGXS := $(shell $(PG_CONFIG) --pgxs) 9 | include $(PGXS) -------------------------------------------------------------------------------- /pg_xid--1.0.sql: -------------------------------------------------------------------------------- 1 | -- use CREATE EXTENSION 2 | \echo Use "CREATE EXTENSION pg_xid" to load this file. \quit 3 | 4 | -- v1 5 | CREATE OR REPLACE FUNCTION xid() RETURNS BYTEA 6 | AS 'pg_xid', 'xid' LANGUAGE C; 7 | 8 | CREATE OR REPLACE FUNCTION xid_encoded() RETURNS TEXT 9 | AS 'pg_xid', 'xid_encoded' LANGUAGE C; 10 | -------------------------------------------------------------------------------- /xid.h: -------------------------------------------------------------------------------- 1 | #ifndef XID_H 2 | #define XID_H 1 3 | 4 | #include 5 | 6 | /* version constants */ 7 | #define XID_VERSION "1.0" 8 | #define XID_VERSION_MAJOR 1 9 | #define XID_VERSION_MINOR 0 10 | #define XID_VERSION_PATCH 0 11 | 12 | /* constants */ 13 | #define XID_HOSTNAME_MAX 30 14 | #define XID_RAW_LEN 12 15 | #define XID_ENCODED_LEN 20 16 | 17 | /* exported function definitions */ 18 | extern void 19 | xid_init(void); 20 | 21 | extern unsigned char * 22 | xid_generate(void); 23 | 24 | extern unsigned char * 25 | xid_encode(unsigned char * id); 26 | 27 | #endif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Vahagn Mkrtchyan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /pg_xid.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 "xid.h" 10 | 11 | #ifdef PG_MODULE_MAGIC 12 | PG_MODULE_MAGIC; 13 | #endif 14 | 15 | extern void _PG_init(void); 16 | extern Datum xid( PG_FUNCTION_ARGS ); 17 | extern Datum xid_encoded( PG_FUNCTION_ARGS ); 18 | 19 | /* 20 | * Module initialization function 21 | */ 22 | void 23 | _PG_init(void) { 24 | xid_init(); 25 | } 26 | 27 | /* 28 | * Generate ID 29 | */ 30 | PG_FUNCTION_INFO_V1( xid ); 31 | Datum 32 | xid( PG_FUNCTION_ARGS ) 33 | { 34 | // Declaration 35 | bytea *result; 36 | unsigned char *id; 37 | 38 | id = xid_generate(); 39 | result = (bytea *)palloc(XID_RAW_LEN + VARHDRSZ); 40 | 41 | SET_VARSIZE(result, XID_RAW_LEN + VARHDRSZ); 42 | memcpy(VARDATA(result), id, XID_RAW_LEN); 43 | 44 | free(id); 45 | PG_RETURN_BYTEA_P(result); 46 | } 47 | 48 | /* 49 | * Generate ID Encoded 50 | */ 51 | PG_FUNCTION_INFO_V1( xid_encoded ); 52 | Datum 53 | xid_encoded( PG_FUNCTION_ARGS ) 54 | { 55 | // Declaration 56 | text *result; 57 | unsigned char *id; 58 | unsigned char *encoded; 59 | 60 | id = xid_generate(); 61 | encoded = xid_encode(id); 62 | result = (text *)palloc(XID_ENCODED_LEN + VARHDRSZ); 63 | 64 | SET_VARSIZE(result, XID_ENCODED_LEN + VARHDRSZ); 65 | memcpy(VARDATA(result), encoded, XID_ENCODED_LEN); 66 | 67 | free(id); 68 | free(encoded); 69 | 70 | PG_RETURN_TEXT_P(result); 71 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pg_xid - globally Unique ID Generator for PostgreSQL 2 | 3 | pg_xid is a globally unique ID generator extension for PostgreSQL. 4 | 5 | It is using Mongo Object ID algorithm to generate globally unique ids: 6 | https://docs.mongodb.org/manual/reference/object-id/ 7 | 8 | - 4-byte value representing the seconds since the Unix epoch, 9 | - 3-byte machine identifier, 10 | - 2-byte process id, and 11 | - 3-byte counter, starting with a random value. 12 | 13 | The binary representation of the id is compatible with Mongo 12 bytes Object IDs. 14 | 15 | UUIDs are 16 bytes (128 bits) and 36 chars as string representation. Twitter Snowflake 16 | ids are 8 bytes (64 bits) but require machine/data-center configuration and/or central 17 | generator servers. xid stands in between with 12 bytes (96 bits). No configuration or central generator server 18 | is required so it can be used directly. 19 | 20 | | Name | Binary Size | Features 21 | |-------------|-------------|---------------- 22 | | [UUID] | 16 bytes | configuration free, not sortable 23 | | [shortuuid] | 16 bytes | configuration free, not sortable 24 | | [Snowflake] | 8 bytes | needs machin/DC configuration, needs central server, sortable 25 | | [MongoID] | 12 bytes | configuration free, sortable 26 | | xid | 12 bytes | configuration free, sortable 27 | 28 | [UUID]: https://en.wikipedia.org/wiki/Universally_unique_identifier 29 | [shortuuid]: https://github.com/stochastic-technologies/shortuuid 30 | [Snowflake]: https://blog.twitter.com/2010/announcing-snowflake 31 | [MongoID]: https://docs.mongodb.org/manual/reference/object-id/ 32 | 33 | Features: 34 | 35 | - Size: 12 bytes (96 bits), smaller than UUID, larger than snowflake 36 | - Non configured, you don't need set a unique machine and/or data center id 37 | - K-ordered 38 | - Embedded time with 1 second precision 39 | - Unicity guaranteed for 16,777,216 (24 bits) unique ids per second and per host/process 40 | - Lock-free (i.e.: unlike UUIDv1 and v2) 41 | 42 | References: 43 | 44 | - https://github.com/rs/xid 45 | - http://www.slideshare.net/davegardnerisme/unique-id-generation-in-distributed-systems 46 | - https://en.wikipedia.org/wiki/Universally_unique_identifier 47 | - https://blog.twitter.com/2010/announcing-snowflake 48 | - Python port by [Graham Abbott](https://github.com/graham): https://github.com/graham/python_xid 49 | 50 | Inspired by [Olivier Poitrey](https://github.com/rs)'s Xid. 51 | 52 | ## Build 53 | 54 | make 55 | make install 56 | 57 | ## Install 58 | 59 | CREATE EXTENSION pg_xid; 60 | 61 | ## Usage 62 | 63 | ```SQL 64 | SELECT xid(); -- returns: BYTEA 65 | 66 | SELECT encode(xid(),'hex'); -- returns: TEXT 67 | ``` 68 | 69 | ## Licenses 70 | 71 | All source code is licensed under the [MIT License](https://raw.github.com/icyberon/pg_xid/master/LICENSE). 72 | -------------------------------------------------------------------------------- /xid.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "postgres.h" 9 | #include "xid.h" 10 | 11 | /* Custom Base32 */ 12 | static const char * encoding = "0123456789abcdefghijklmnopqrstuv"; 13 | 14 | /* Global ID Counter */ 15 | static uint32_t id_counter; 16 | static pid_t pid; 17 | static unsigned char hostname[3]; 18 | 19 | /* Swap Endianness */ 20 | static inline uint32_t 21 | swap_endian(uint32_t val) { 22 | return (val<<24) | ((val<<8) & 0x00ff0000) | ((val>>8) & 0x0000ff00) | (val>>24); 23 | } 24 | 25 | unsigned int random_unsigned_int(void) 26 | { 27 | unsigned int r; 28 | if (!pg_strong_random(&r, sizeof(r))) 29 | { 30 | elog(ERROR, "pg_xid: pg_strong_random failed!"); 31 | return 0; 32 | } 33 | return r; 34 | } 35 | 36 | extern void 37 | xid_init() { 38 | char name[XID_HOSTNAME_MAX]; 39 | unsigned char * name_md5; 40 | 41 | name_md5 = malloc(MD5_DIGEST_LENGTH); 42 | id_counter = random_unsigned_int(); 43 | 44 | // Hostname md5 45 | gethostname((char*)name, XID_HOSTNAME_MAX); 46 | MD5((unsigned char*)&name, XID_HOSTNAME_MAX, name_md5); 47 | 48 | memcpy(&hostname, name_md5, 3); 49 | free(name_md5); 50 | 51 | // PID 52 | pid = getpid(); 53 | } 54 | 55 | /* Generate ID */ 56 | extern unsigned char * 57 | xid_generate() { 58 | unsigned char id[XID_RAW_LEN]; 59 | unsigned char* result; 60 | 61 | result = malloc(sizeof(id)); 62 | 63 | // Timestamp 64 | uint32_t t = time(NULL); 65 | t = swap_endian(t); 66 | memcpy(&id, &t, 4); 67 | 68 | // Hostname 69 | memcpy(&id[4], hostname, 3); 70 | 71 | // PID 72 | id[7] = (unsigned char)(pid >> 8); 73 | id[8] = (unsigned char)(pid); 74 | 75 | // Counter 76 | id_counter++; 77 | id[9] = (unsigned char)(id_counter >> 16); 78 | id[10] = (unsigned char)(id_counter >> 8); 79 | id[11] = (unsigned char)(id_counter); 80 | 81 | memcpy(result, &id, XID_RAW_LEN); 82 | 83 | return result; 84 | } 85 | 86 | /* Generate ID */ 87 | extern unsigned char * 88 | xid_encode(unsigned char * id) { 89 | unsigned char dst[XID_ENCODED_LEN]; 90 | unsigned char * result; 91 | 92 | result = malloc(XID_ENCODED_LEN); 93 | 94 | dst[0] = encoding[ id[0]>>3 ]; 95 | dst[1] = encoding[ ((id[1]>>6) & 0x1F) | ((id[0]<<2) & 0x1F) ]; 96 | dst[2] = encoding[ (id[1]>>1) & 0x1F]; 97 | dst[3] = encoding[ ((id[2]>>4) & 0x1F) | ((id[1]<<4) & 0x1F) ]; 98 | dst[4] = encoding[ id[3]>>7 | ((id[2]<<1) & 0x1F) ]; 99 | dst[5] = encoding[ (id[3]>>2) & 0x1F ]; 100 | dst[6] = encoding[ id[4]>>5 | ((id[3]<<3) & 0x1F) ]; 101 | dst[7] = encoding[ id[4] & 0x1F ]; 102 | dst[8] = encoding[ id[5]>>3 ]; 103 | dst[9] = encoding[ ((id[6]>>6) & 0x1F) | ((id[5]<<2) & 0x1F) ]; 104 | dst[10] = encoding[ (id[6]>>1) & 0x1F ]; 105 | dst[11] = encoding[ ((id[7]>>4) & 0x1F) | ((id[6]<<4) & 0x1F) ]; 106 | dst[12] = encoding[ id[8]>>7 | ((id[7]<<1) & 0x1F) ]; 107 | dst[13] = encoding[ (id[8]>>2) & 0x1F]; 108 | dst[14] = encoding[ (id[9]>>5) | ((id[8]<<3) & 0x1F) ]; 109 | dst[15] = encoding[ id[9] & 0x1F ]; 110 | dst[16] = encoding[ id[10]>>3 ]; 111 | dst[17] = encoding[ ((id[11]>>6) & 0x1F) | ((id[10]<<2) & 0x1F) ]; 112 | dst[18] = encoding[ (id[11]>>1) & 0x1F ]; 113 | dst[19] = encoding[ (id[11]<<4) & 0x1F ]; 114 | 115 | memcpy(result, &dst, XID_ENCODED_LEN); 116 | 117 | return result; 118 | } 119 | --------------------------------------------------------------------------------