├── README.md ├── config └── snowflake.php ├── .gitignore ├── .styleci.yml ├── src ├── Connections │ ├── ConnectionInterface.php │ ├── BaseConnection.php │ ├── MySqlConnection.php │ └── PostgresConnection.php ├── Snowflake.php ├── ForeignSnowflakeColumnDefinition.php └── SnowflakeServiceProvider.php ├── phpunit.xml ├── LICENSE └── composer.json /README.md: -------------------------------------------------------------------------------- 1 | # laravel-snowflake -------------------------------------------------------------------------------- /config/snowflake.php: -------------------------------------------------------------------------------- 1 | env('SNOWFLAKE_SHARED_ID', 1), 5 | 'epoch_start_of_time' => env('SNOWFLAKE_EPOCH_START_OF_TIME', 1314220021721), 6 | ]; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | node_modules/ 3 | npm-debug.log 4 | yarn-error.log 5 | storage/*.key 6 | .env 7 | .phpunit.result.cache 8 | .php-cs-fixer.php 9 | .php-cs-fixer.cache 10 | composer.lock 11 | -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | php: 2 | preset: laravel 3 | finder: 4 | not-name: 5 | - index.php 6 | - server.php 7 | enabled: 8 | - fully_qualified_strict_types 9 | js: 10 | finder: 11 | not-name: 12 | - webpack.mix.js 13 | exclude: 14 | - public 15 | not-path: 16 | - dist 17 | css: true -------------------------------------------------------------------------------- /src/Connections/ConnectionInterface.php: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 17 | src 18 | 19 | 20 | 21 | 22 | tests 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 userlynk 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 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "userlynk/laravel-snowflake", 3 | "type": "library", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "Adam Campbell", 8 | "email": "adam@hotmeteor.com" 9 | }, 10 | { 11 | "name": "Yaz Jallad", 12 | "email": "yazjallad@gmail.com" 13 | } 14 | ], 15 | "require": { 16 | "php": "^8.0", 17 | "ext-pdo": "*" 18 | }, 19 | "require-dev": { 20 | "nunomaduro/collision": "^4.0|^5.1", 21 | "orchestra/testbench": "^5.0|^6.0", 22 | "phpunit/phpunit": "^8.0|^9.0" 23 | }, 24 | "autoload": { 25 | "psr-4": { 26 | "Userlynk\\Snowflake\\": "src" 27 | } 28 | }, 29 | "autoload-dev": { 30 | "psr-4": { 31 | "Userlynk\\Snowflake\\Tests\\": "tests/" 32 | } 33 | }, 34 | "scripts": { 35 | "test": "vendor/bin/phpunit" 36 | }, 37 | "extra": { 38 | "laravel": { 39 | "providers": [ 40 | "Userlynk\\Snowflake\\SnowflakeServiceProvider" 41 | ], 42 | "aliases": { 43 | "Snowflake": "Userlynk\\Snowflake\\Snowflake" 44 | } 45 | } 46 | }, 47 | "config": { 48 | "sort-packages": true 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/SnowflakeServiceProvider.php: -------------------------------------------------------------------------------- 1 | publishConfig(); 17 | 18 | Snowflake::register(); 19 | } 20 | } 21 | 22 | public function register() 23 | { 24 | $this->mergeConfig(); 25 | 26 | $this->app->bind(ConnectionInterface::class, function ($app) { 27 | return $app['db.connection']->getDriverName() === 'pgsql' 28 | ? new PostgresConnection() 29 | : new MySqlConnection(); 30 | }); 31 | } 32 | 33 | protected function publishConfig() 34 | { 35 | $this->publishes([ 36 | __DIR__.'/../config/snowflake.php' => config_path('snowflake.php'), 37 | ]); 38 | } 39 | 40 | protected function mergeConfig() 41 | { 42 | $this->mergeConfigFrom( 43 | __DIR__.'/../config/snowflake.php', 44 | 'snowflake' 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Connections/BaseConnection.php: -------------------------------------------------------------------------------- 1 | prepareDatabase(); 16 | $this->registerEvents(); 17 | $this->registerGrammarMacros(); 18 | $this->registerBlueprintMacros(); 19 | $this->applyTriggers(); 20 | } 21 | 22 | /** 23 | * @return void 24 | */ 25 | public function registerBlueprintMacros(): void 26 | { 27 | Blueprint::macro('snowflake', function ($column = 'sid') { 28 | $column = $this->unsignedBigInteger($column)->unique(); 29 | 30 | $this->addCommand('removeSidTrigger'); 31 | $this->addCommand('addSidTrigger'); 32 | 33 | return $column; 34 | }); 35 | 36 | Blueprint::macro('foreignSnowflake', function ($column) { 37 | return $this->addColumnDefinition(new ForeignSnowflakeColumnDefinition($this, [ 38 | 'type' => 'bigInteger', 39 | 'name' => $column, 40 | 'autoIncrement' => false, 41 | 'unsigned' => true, 42 | ])); 43 | }); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Connections/MySqlConnection.php: -------------------------------------------------------------------------------- 1 | wrapStatement('DROP FUNCTION IF EXISTS `sid_generator`$$'); 27 | 28 | $this->wrapStatement(" 29 | DELIMITER $$ 30 | CREATE FUNCTION `sid_generator`() RETURNS BIGINT(20) 31 | DETERMINISTIC 32 | BEGIN 33 | DECLARE epoch BIGINT(20); 34 | DECLARE current_ms BIGINT(20); 35 | DECLARE node INTEGER; 36 | DECLARE incr BIGINT(20); 37 | DECLARE schema_node INTEGER default 1; 38 | 39 | SET node = ${shared_id}; 40 | SET current_ms = round(UNIX_TIMESTAMP(CURTIME(4)) * 1000); 41 | SET epoch = {$epoch_start_of_time}; 42 | 43 | SELECT LAST_INSERT_ID() INTO incr; 44 | RETURN (current_ms - epoch) << 22 | (node << 12) | (incr % 4096); 45 | END$$ 46 | DELIMITER ; 47 | "); 48 | }); 49 | } 50 | 51 | public function registerGrammarMacros(): void 52 | { 53 | MySqlGrammar::macro('compileRemoveSidTrigger', function (Blueprint $blueprint, Fluent $command) { 54 | return sprintf( 55 | 'DROP TRIGGER IF EXISTS %;', 56 | 'trigger_set_sid_'.$blueprint->getTable(), 57 | ); 58 | }); 59 | 60 | MySqlGrammar::macro('compileAddSidTrigger', function (Blueprint $blueprint, Fluent $command) { 61 | return sprintf( 62 | ' 63 | CREATE TRIGGER %s 64 | BEFORE INSERT 65 | ON %s FOR EACH ROW 66 | BEGIN 67 | SET NEW.sid = sid_generator(); 68 | END; 69 | ', 70 | 'trigger_set_sid_'.$blueprint->getTable(), 71 | $this->wrapTable($blueprint), 72 | $this->wrapTable($blueprint), 73 | ); 74 | }); 75 | } 76 | 77 | public function applyTriggers() 78 | { 79 | // TODO: Implement applyTriggers() method. 80 | } 81 | 82 | /** 83 | * @param $statement 84 | * @return \Illuminate\Database\Query\Expression 85 | */ 86 | protected function wrapStatement($statement): \Illuminate\Database\Query\Expression 87 | { 88 | return DB::raw($statement); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Connections/PostgresConnection.php: -------------------------------------------------------------------------------- 1 | wrapTable($blueprint), 81 | ); 82 | }); 83 | 84 | PostgresGrammar::macro('compileAddSidTrigger', function (Blueprint $blueprint, Fluent $command) { 85 | return sprintf( 86 | 'create trigger trigger_set_sid before insert on %s for each row execute procedure set_sid();', 87 | $this->wrapTable($blueprint), 88 | ); 89 | }); 90 | } 91 | 92 | /** 93 | * Apply triggers to any existing tables with a "sid" column. 94 | * 95 | * @return void 96 | */ 97 | public function applyTriggers() 98 | { 99 | DB::statement(" 100 | DO $$ 101 | DECLARE 102 | t text; 103 | BEGIN 104 | FOR t IN 105 | SELECT table_name FROM information_schema.columns WHERE column_name = 'sid' 106 | LOOP 107 | EXECUTE format('DROP TRIGGER IF EXISTS trigger_set_sid ON %I;', t,t); 108 | EXECUTE format('CREATE TRIGGER trigger_set_sid BEFORE INSERT ON %I FOR EACH ROW EXECUTE PROCEDURE set_sid();', t,t); 109 | END loop; 110 | END; 111 | $$ language 'plpgsql'; 112 | "); 113 | } 114 | } 115 | --------------------------------------------------------------------------------