├── .gitignore ├── README.md ├── composer.json ├── config └── config.php ├── phpunit.xml.dist ├── resources └── views │ ├── factory.blade.php │ ├── migration.blade.php │ ├── model.blade.php │ ├── nova.blade.php │ └── test.blade.php ├── src ├── Commands │ └── Laravelize.php ├── Database.php ├── Factory.php ├── Filesystem.php ├── LaravelizerServiceProvider.php ├── Migration.php ├── Nova.php ├── Stub.php └── Types │ ├── EnumType.php │ └── GeometryType.php └── tests ├── ColumnClassifierTest.php ├── DatabaseTest.php ├── FilesystemTest.php ├── StubTest.php ├── TestCase.php └── sakila-db ├── sakila-data.sql ├── sakila-schema.sql └── sakila.mwb /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | /build/ 3 | composer.lock 4 | .phpunit.result.cache 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # laravelizer (a work in progress) 2 | ### Create Models, Migrations, and Factories for any existing MySQL database 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/edgrosvenor/laravelizer.svg?style=flat-square)](https://packagist.org/packages/edgrosvenor/laravelizer) 4 | [![StyleCI](https://github.styleci.io/repos/234602553/shield?branch=master)](https://github.styleci.io/repos/234602553) 5 | ![Build Status](https://app.chipperci.com/projects/f7090772-1ef1-443e-acfc-8cb77cb84b51/status/master) 6 | 7 | Whether you want to migrate away from another framework to Laravel or you just want to connect a Laravel installation to your database, this package makes it really easy. 8 | 9 | Just define the connection to your database as you would any Laravel database. It doesn't have to be the default connection, but it can be. Then install this package and follow the instructions to create migrations, models, factories, tests, and / or Nova resources based on the structure of and existing data within your database. 10 | 11 | ## Installation 12 | Create the Laravel installation into which you want to add model, migrations, and factories for your existing (presumably non-Laravel) database. Then install this package in that Laravel installation like so: 13 | 14 | ```bash 15 | composer require edgrosvenor/laravelizer --dev 16 | ``` 17 | 18 | ## Configuration 19 | By default, models will be created in your app root, factories in database/factories, and migrations in database/migrations. Optionally Nova resources can be added at app/Nova and tests can be generated at tests/unit/. You can choose whether to disable any of these or change the paths at which they're created by publishing `php artisan vendor:publish` and editing config/laravelizer.php. 20 | 21 | When you publish assets, the stubs used for each component are also published. We use blade templates for our stubs so they are pretty easy to edit if you want to change things up a bit. 22 | 23 | ## Usage 24 | 25 | ```bash 26 | php artisan laravelize {table_name?} {--connection=} {--force} 27 | ``` 28 | If you do not specify a table name, we'll just do all of them. 29 | 30 | ### Options 31 | `--force` Overwrite any existing files related to the table. 32 | `--connection=` Name of the database connection you want to use if not the default connection. 33 | 34 | ## Contributing 35 | 36 | I'd love some help to make this package super useful for the community. Submit a PR to improve the code of the README or just open an issue letting me know what you'd like to see added. 37 | 38 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "edgrosvenor/laravelizer", 3 | "description": "Create Laravel models, migrations, and factories for an existing MySQL database", 4 | "type": "library", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Ed Grosvenor", 9 | "email": "ed.grosvenor@wickedviral.com" 10 | } 11 | ], 12 | "require": { 13 | "php": "^7.2", 14 | "illuminate/support": "^6.0", 15 | "illuminate/console": "^6.0", 16 | "illuminate/filesystem": "^6.0", 17 | "doctrine/dbal": "^2.10", 18 | "edgrosvenor/column-classifier": "^1.0", 19 | "ext-json": "*" 20 | }, 21 | "require-dev": { 22 | "orchestra/testbench": "^4.5" 23 | }, 24 | "autoload": { 25 | "psr-4": { 26 | "Laravelizer\\": "src/" 27 | } 28 | }, 29 | "autoload-dev": { 30 | "psr-4": { 31 | "Tests\\": "tests/", 32 | "App\\": "vendor/orchestra/testbench-core/laravel/app" 33 | } 34 | }, 35 | "extra": { 36 | "laravel": { 37 | "providers": "Laravelizer\\LaravelizerServiceProvider" 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /config/config.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'suppress' => env('LARAVELIZER_MODEL_SUPPRESS', false), 6 | 'path' => env('LARAVELIZER_MODEL_PATH', app_path()), 7 | 'stub' => env('LARAVELIZER_MODEL_STUB', 'laravelizer::model'), 8 | ], 9 | 'migration' => [ 10 | 'suppress' => env('LARAVELIZER_MIGRATION_SUPPRESS', false), 11 | 'path' => env('LARAVELIZER_MIGRATION_PATH', database_path('migrations')), 12 | 'stub' => env('LARAVELIZER_MIGRATION_STUB', 'laravelizer::migration'), 13 | ], 14 | 'factory' => [ 15 | 'suppress' => env('LARAVELIZER_FACTORY_SUPPRESS', false), 16 | 'path' => env('LARAVELIZER_FACTORY_PATH', database_path('factories')), 17 | 'stub' => env('LARAVELIZER_FACTORY_STUB', 'laravelizer::factory'), 18 | ], 19 | 'nova' => [ 20 | 'suppress' => env('LARAVELIZER_NOVA_SUPPRESS', false), 21 | 'path' => env('LARAVELIZER_NOVA_PATH', app_path('Nova')), 22 | 'stub' => env('LARAVELIZER_NOVA_STUB', 'laravelizer::nova'), 23 | ], 24 | 'test' => [ 25 | 'suppress' => env('LARAVELIZER_TEST_SUPPRESS', true), 26 | 'path' => env('LARAVELIZER_TEST_PATH', base_path('tests/unit')), 27 | 'stub' => env('LARAVELIZER_TEST_STUB', 'laravelizer::test'), 28 | ], 29 | ]; 30 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | tests 15 | 16 | 17 | 18 | 19 | src/ 20 | 21 | 22 | 23 | 24 | db 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /resources/views/factory.blade.php: -------------------------------------------------------------------------------- 1 | {!! $php_open !!} 2 | 3 | /** @var \Illuminate\Database\Eloquent\Factory $factory */ 4 | use {{ $model_namespace }}\{{ $model_name }}; 5 | use Illuminate\Support\Str; 6 | use Faker\Generator as Faker; 7 | 8 | $factory->define({{ $model_name }}::class, function (Faker $faker) { 9 | return [ 10 | @foreach ($columns as $column) 11 | "{{$column['name']}}" => {!! $column['factory'] ?? '' !!}, 12 | @endforeach 13 | ]; 14 | }); -------------------------------------------------------------------------------- /resources/views/migration.blade.php: -------------------------------------------------------------------------------- 1 | {!! $php_open !!} 2 | 3 | use Illuminate\Database\Migrations\Migration; 4 | use Illuminate\Database\Schema\Blueprint; 5 | use Illuminate\Support\Facades\Schema; 6 | 7 | class {{ $class_name }} extends Migration 8 | { 9 | public function up() 10 | { 11 | if (!Schema::connection('{{$connection}}')->hasTable('{{$table}}')) { 12 | Schema::connection('{{$connection}}')->create('{{ $table }}', function (Blueprint $table) { 13 | @foreach ($columns as $k => $v) 14 | {!! $v['migration'] !!} 15 | @endforeach 16 | @if ($timestamps) 17 | $table->timestamps(); 18 | @endif 19 | @if ($soft_deletes) 20 | $table->softDeletes(); 21 | @endif 22 | }); 23 | } 24 | @if ($add_timestamps) 25 | Schema::connection('{{$connection}}')->table(function (Blueprint $table) { 26 | $table->timestamps(); 27 | }); 28 | @endif 29 | @if ($add_soft_deletes) 30 | Schema::connection('{{$connection}}')->table(function (Blueprint $table) { 31 | $table->timestamps(); 32 | }); 33 | @endif 34 | } 35 | 36 | public function down() 37 | { 38 | Schema::dropIfExists('{{ $table }}'); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /resources/views/model.blade.php: -------------------------------------------------------------------------------- 1 | {!! $php_open !!} 2 | 3 | namespace {{ $model_namespace }}; 4 | 5 | use Illuminate\Database\Eloquent\Model; 6 | @if ($soft_delete) 7 | use Illuminate\Database\Eloquent\SoftDeletes; 8 | @endif 9 | 10 | class {{ $model_name }} extends Model 11 | { 12 | @if ($soft_delete) 13 | use SoftDeletes; 14 | @endif 15 | 16 | protected $connection = "{{ $connection }}"; 17 | protected $table = "{{ $table }}"; 18 | 19 | protected $fillable = []; 20 | 21 | protected $casts = [ 22 | @foreach ($casts as $k => $v) 23 | "{{$k}}" => "{{$v}}", 24 | @endforeach 25 | ]; 26 | 27 | protected $attributes = [ 28 | @foreach ($attributes as $k => $v) 29 | "{{$k}}" => "{{$v}}", 30 | @endforeach 31 | ]; 32 | 33 | @if (!$timestamps) 34 | public $timestamps = false; 35 | @endif 36 | } 37 | -------------------------------------------------------------------------------- /resources/views/nova.blade.php: -------------------------------------------------------------------------------- 1 | {!! $php_open !!} 2 | 3 | namespace App\Nova; 4 | 5 | use App\Nova\Resource; 6 | use Illuminate\Http\Request; 7 | use Laravel\Nova\Fields\Boolean; 8 | use Laravel\Nova\Fields\Code; 9 | use Laravel\Nova\Fields\DateTime; 10 | use Laravel\Nova\Fields\HasMany; 11 | use Laravel\Nova\Fields\Number; 12 | use Laravel\Nova\Fields\Text; 13 | 14 | class {{ $model_name }} extends Resource 15 | { 16 | /** @var string */ 17 | public static $model = '{{ $model_namespace }}\{{ $model_name }}'; 18 | /** @var string */ 19 | public static $group = ''; 20 | /** @var boolean */ 21 | public static $displayInNavigation = true; 22 | /** @var array */ 23 | public static $with = []; 24 | /** @var array */ 25 | public static $orderBy = []; 26 | 27 | /** 28 | * The single value that should be used to represent the resource when being displayed. 29 | * 30 | * @var string 31 | */ 32 | public static $title = 'id'; 33 | 34 | /** 35 | * The columns that should be searched. 36 | * 37 | * @var array 38 | */ 39 | public static $search = [ 40 | 'id', 41 | ]; 42 | 43 | 44 | /** 45 | * Get the fields displayed by the resource. 46 | * 47 | * @param \Illuminate\Http\Request $request 48 | * @return array 49 | */ 50 | public function fields(Request $request) 51 | { 52 | return [ 53 | @foreach ($columns as $column) 54 | {!! $column['nova'] !!}, 55 | @endforeach 56 | ]; 57 | } 58 | 59 | /** 60 | * Get the cards available for the request. 61 | * 62 | * @param \Illuminate\Http\Request $request 63 | * @return array 64 | */ 65 | public function cards(Request $request) 66 | { 67 | return []; 68 | } 69 | 70 | /** 71 | * Get the filters available for the resource. 72 | * 73 | * @param \Illuminate\Http\Request $request 74 | * @return array 75 | */ 76 | public function filters(Request $request) 77 | { 78 | return []; 79 | } 80 | 81 | /** 82 | * Get the lenses available for the resource. 83 | * 84 | * @param \Illuminate\Http\Request $request 85 | * @return array 86 | */ 87 | public function lenses(Request $request) 88 | { 89 | return []; 90 | } 91 | 92 | /** 93 | * Get the actions available for the resource. 94 | * 95 | * @param \Illuminate\Http\Request $request 96 | * @return array 97 | */ 98 | public function actions(Request $request) 99 | { 100 | return []; 101 | } 102 | } -------------------------------------------------------------------------------- /resources/views/test.blade.php: -------------------------------------------------------------------------------- 1 | {!! $php_open !!} 2 | 3 | namespace Tests\Unit; 4 | 5 | use PHPUnit\Framework\TestCase; 6 | 7 | class {{ $model_name }}Test extends TestCase 8 | { 9 | public funciton setUp(): void 10 | { 11 | parent::setUp(); 12 | config(['database.default' => '{{ $connection }}']); 13 | } 14 | 15 | /** @test */ 16 | public function test{{ $model_name }}() 17 | { 18 | $record = factory({{ $model_name }}::class)->create(); 19 | $this->assertDatabaseHas('{{ $table }}', $record->toArray(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Commands/Laravelize.php: -------------------------------------------------------------------------------- 1 | written = collect([]); 34 | } 35 | 36 | public function handle() 37 | { 38 | $this->connection = $this->hasOption('connection') && !empty($this->option('connection')) ? $this->option('connection') : config('database.default'); 39 | $this->force = $this->hasOption('force') && !empty($this->option('force')); 40 | $this->tables = $this->argument('table') === '___*___' ? $this->getAllTableNames() : [$this->argument('table')]; 41 | 42 | foreach ($this->tables as $table) { 43 | if ($table !== 'migrations') { 44 | $this->laravelize($table); 45 | } 46 | } 47 | } 48 | 49 | public function laravelize($table) 50 | { 51 | $this->class_name = $this->ask('What do you want to call the model we are creating from table '.$table.'?', Str::singular(Str::studly($table))); 52 | 53 | foreach ($this->components as $component) { 54 | if (config('laravelizer.'.$component.'.suppress')) { 55 | $this->line(''.ucfirst($component).' Skipped: '.$this->getComponentPath($component, $table)); 56 | continue; 57 | } 58 | if (file_exists($this->getComponentPath($component, $table)) && !$this->force) { 59 | $this->line(''.ucfirst($component).' Already Exists: '.$this->getComponentPath($component, $table)); 60 | continue; 61 | } 62 | $this->line(''.ucfirst($component).' Written: '.$this->getComponentPath($component, $table)); 63 | 64 | $stub = new Stub(); 65 | $db = new Database($this->connection); 66 | 67 | $stub->setTable($table); 68 | $stub->setConnection($this->connection); 69 | $stub->setColumns($db->getColumns($table)); 70 | $stub->setOptions($this->option()); 71 | $stub->setModelClassName($this->class_name); 72 | $stub->setModelNamespace($this->getNamespaceFromPath($this->getComponentPath('model', $table))); 73 | $stub->setSoftDeletes($stub->columns->contains('deleted_at')); 74 | 75 | $fs = new Filesystem(); 76 | $fs->write($this->getComponentPath($component, $table), $stub->$component($this->getComponentPath($component, $table))); 77 | $this->written->push($this->getComponentPath($component, $table)); 78 | } 79 | } 80 | 81 | protected function getAllTableNames() 82 | { 83 | $db = new Database($this->connection); 84 | 85 | return $db->getTables(); 86 | } 87 | 88 | protected function getComponentPaths($component, $table) 89 | { 90 | return [ 91 | 'model' => config('laravelizer.'.$component.'.path').DIRECTORY_SEPARATOR.$this->class_name.'.php', 92 | 'migration' => config('laravelizer.'.$component.'.path').DIRECTORY_SEPARATOR.date('Y_m_d_Hms').'_create_'.$table.'_table.php', 93 | 'factory' => config('laravelizer.'.$component.'.path').DIRECTORY_SEPARATOR.$this->class_name.'Factory.php', 94 | 'nova' => config('laravelizer.'.$component.'.path').DIRECTORY_SEPARATOR.$this->class_name.'.php', 95 | 'test' => config('laravelizer.'.$component.'.path').DIRECTORY_SEPARATOR.$this->class_name.'Test.php', 96 | ]; 97 | } 98 | 99 | protected function getComponentPath($component, $table) 100 | { 101 | return $this->getComponentPaths($component, $table)[$component]; 102 | } 103 | 104 | protected function getNamespaceFromPath($path, $root = null): string 105 | { 106 | $path = str_replace(base_path(), '', $path); 107 | $path = substr($path, 0, strrpos($path, '/')); 108 | $ns = is_null($root) ? [] : [$root]; 109 | foreach (explode('/', $path) as $k) { 110 | array_push($ns, Str::studly($k)); 111 | } 112 | 113 | return trim(implode('\\', $ns), '\\'); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/Database.php: -------------------------------------------------------------------------------- 1 | connection = $connection; 14 | 15 | DB::connection()->getDoctrineSchemaManager()->getDatabasePlatform()->registerDoctrineTypeMapping('enum', 'customEnum'); 16 | DB::connection()->getDoctrineSchemaManager()->getDatabasePlatform()->registerDoctrineTypeMapping('geometry', 'customGeometry'); 17 | } 18 | 19 | public function getTables() 20 | { 21 | return DB::connection($this->connection)->getDoctrineSchemaManager()->listTableNames(); 22 | } 23 | 24 | public function getColumnNames($table) 25 | { 26 | return array_keys(DB::connection($this->connection)->getDoctrineSchemaManager()->listTableColumns($table)); 27 | } 28 | 29 | public function getColumns($table) 30 | { 31 | $columns = collect([]); 32 | foreach (DB::connection($this->connection)->getDoctrineSchemaManager()->listTableColumns($table) as $column) { 33 | $col = [ 34 | 'connection' => $this->connection, 35 | 'table' => $table, 36 | 'name' => $column->getName(), 37 | 'type' => $column->getType()->getName(), 38 | 'length' => $column->getLength(), 39 | 'precision' => $column->getPrecision(), 40 | 'scale' => $column->getScale(), 41 | 'unsigned' => $column->getUnsigned(), 42 | 'fixed' => $column->getFixed(), 43 | 'default' => $column->getDefault(), 44 | 'autoincrement' => $column->getAutoincrement(), 45 | 'notnull' => $column->getNotNull(), 46 | 'options' => $column->getPlatformOptions(), 47 | 'definition' => $column->getColumnDefinition(), 48 | 'comment' => $column->getComment(), 49 | ]; 50 | 51 | $migration = new Migration($col); 52 | $col['migration'] = $migration->execute(); 53 | 54 | $factory = new Factory($col); 55 | $col['factory'] = $factory->execute(); 56 | 57 | $nova = new Nova($col); 58 | $col['nova'] = $nova->execute(); 59 | 60 | $columns->push($col); 61 | } 62 | 63 | return $columns; 64 | } 65 | 66 | public function getForeignKeyRestraints($table) 67 | { 68 | $constraints = collect([]); 69 | foreach (DB::connection($this->connection)->getDoctrineSchemaManager()->listTableForeignKeys($table) as $constraint) { 70 | if (is_object($constraint)) { 71 | $constraints->push([ 72 | 'name' => $constraint->getName() ?? null, 73 | 'local_column' => $constraint->getLocalColumns()[0] ?? null, 74 | 'foreign_table' => $constraint->getForeignTableName() ?? null, 75 | 'foreign_column' => $constraint->getForeigncolumns()[0], 76 | 'onDelete' => $constraint->getOptions()['onDelete'] ?? null, 77 | 'onUpdate' => $constraint->getOptions()['onUpdate'] ?? null, 78 | ]); 79 | } 80 | } 81 | 82 | return $constraints; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Factory.php: -------------------------------------------------------------------------------- 1 | 'firstName', 18 | 'last_name' => 'lastName', 19 | 'full_name' => 'name', 20 | 'phone' => 'phoneNumber', 21 | 'email' => 'email', 22 | 'city' => 'city', 23 | 'state' => 'state', 24 | 'state_abbr' => 'stateAbbr', 25 | 'zip_code' => 'zipcode', 26 | 'country' => 'country', 27 | 'country_code' => 'countryCode', 28 | 'currency' => 'currencyCode', 29 | 'company' => 'companyName', 30 | 'job_title' => 'jobTitle', 31 | 'sentence' => 'sentence', 32 | 'paragraph' => 'paragraph', 33 | 'html' => 'html', 34 | 'word' => 'word', 35 | 36 | ]; 37 | 38 | public function __construct($column) 39 | { 40 | $this->column = $column; 41 | $this->name = strtolower($column['name']); 42 | } 43 | 44 | public function execute() 45 | { 46 | $type = $this->column['type']; 47 | 48 | return method_exists($this, $type) ? $this->$type() : $this->missingTypeMethod(); 49 | } 50 | 51 | public function string() 52 | { 53 | if ($this->name === 'deleted_at') { 54 | return ''; 55 | } 56 | if (Str::contains($this->name, 'email')) { 57 | return '$faker->email'; 58 | } 59 | if (Str::containsAll($this->name, ['first', 'name'])) { 60 | return '$faker->firstName'; 61 | } 62 | if (Str::containsAll($this->name, ['last', 'name'])) { 63 | return '$faker->lastName'; 64 | } 65 | if (Str::contains($this->name, ['username', 'uname', 'nickname'])) { 66 | return '$faker->userName'; 67 | } 68 | if (Str::contains($this->name, ['website', 'domain'])) { 69 | return '$faker->domainName'; 70 | } 71 | if (Str::contains($this->name, ['url'])) { 72 | return '$faker->url'; 73 | } 74 | if (Str::contains($this->name, ['password'])) { 75 | return '$faker->password'; 76 | } 77 | if (Str::contains($this->name, ['address'])) { 78 | return '$faker->address'; 79 | } 80 | 81 | $classifier = new Classifier($this->getSample()); 82 | 83 | return '$faker->'.$this->classified[(string) $classifier->execute() ?? 'word']; 84 | } 85 | 86 | public function simple_array() 87 | { 88 | return 'join(",", $faker->words(10))'; 89 | } 90 | 91 | public function text() 92 | { 93 | return '$faker->paragraph()'; 94 | } 95 | 96 | public function geometry() 97 | { 98 | return 'ST_GeomFromText(\'POINT(1 1)\')'; 99 | } 100 | 101 | public function boolean() 102 | { 103 | return '$faker->boolean()'; 104 | } 105 | 106 | public function smallint() 107 | { 108 | return '$faker->numberBetween('.min($this->getSample()->toArray()).', '.max($this->getSample()->toArray()).')'; 109 | } 110 | 111 | public function integer() 112 | { 113 | return '$faker->numberBetween('.min($this->getSample()->toArray()).', '.max($this->getSample()->toArray()).')'; 114 | } 115 | 116 | public function bigint() 117 | { 118 | return '$faker->numberBetween('.min($this->getSample()->toArray()).', '.max($this->getSample()->toArray()).')'; 119 | } 120 | 121 | public function blob() 122 | { 123 | return ''; 124 | } 125 | 126 | public function enum() 127 | { 128 | $array = implode('","', $this->getSample()->toArray()); 129 | 130 | return '$faker->randomElement([" '.$array.'"])'; 131 | } 132 | 133 | public function datetime() 134 | { 135 | return '$faker->dateTime()'; 136 | } 137 | 138 | public function date() 139 | { 140 | return '$faker->date()'; 141 | } 142 | 143 | public function float() 144 | { 145 | $exp = $this->column['scale'] - $this->column['precision']; 146 | $max = pow(10, $exp) - 1; 147 | 148 | return '$faker->randomFloat('.$this->column['scale'].', 0, '.$max.')'; 149 | } 150 | 151 | public function decimal() 152 | { 153 | $exp = $this->column['precision'] - $this->column['scale']; 154 | $max = pow(10, $exp) - 1; 155 | 156 | return '$faker->randomFloat('.$this->column['scale'].', 0, '.$max.')'; 157 | } 158 | 159 | public function getSample(): Collection 160 | { 161 | return DB::connection($this->column['connection']) 162 | ->table($this->column['table']) 163 | ->select($this->column['name']) 164 | ->groupBy($this->column['name']) 165 | ->limit(100)->get() 166 | ->pluck($this->column['name']); 167 | } 168 | 169 | private function missingTypeMethod() 170 | { 171 | dd('Missing a faker generator for '.$this->column['type']); 172 | 173 | return '$faker->word'; 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/Filesystem.php: -------------------------------------------------------------------------------- 1 | ensureDirectoryExists($path); 19 | File::put($path, $contents); 20 | } 21 | 22 | public function read($path): string 23 | { 24 | return File::get($path); 25 | } 26 | 27 | public function ensureDirectoryExists($path): void 28 | { 29 | if ($this->isFileName($path)) { 30 | $path = substr($path, 0, strrpos($path, '/')); 31 | } 32 | if (!File::isDirectory($path)) { 33 | File::makeDirectory($path, 0777, $recursive = true, $force = true); 34 | } 35 | } 36 | 37 | private function isFileName($path) 38 | { 39 | return count(explode('.', $path)) > 1; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/LaravelizerServiceProvider.php: -------------------------------------------------------------------------------- 1 | loadViewsFrom(__DIR__.'/../resources/views', 'laravelizer'); 16 | 17 | if ($this->app->runningInConsole()) { 18 | $this->publishes([ 19 | __DIR__.'/../config/config.php' => config_path('laravelizer.php'), 20 | ], 'config'); 21 | 22 | $this->publishes([ 23 | __DIR__.'/../resources/views' => resource_path('views/vendor/laravelizer'), 24 | ], 'views'); 25 | 26 | $this->commands([ 27 | Laravelize::class, 28 | ]); 29 | } 30 | if (!Type::hasType('customEnum')) { 31 | Type::addType('customEnum', EnumType::class); 32 | } 33 | if (!Type::hasType('customGeometry')) { 34 | Type::addType('customGeometry', GeometryType::class); 35 | } 36 | } 37 | 38 | public function register() 39 | { 40 | $this->mergeConfigFrom(__DIR__.'/../config/config.php', 'laravelizer'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Migration.php: -------------------------------------------------------------------------------- 1 | column = $column; 14 | } 15 | 16 | public function execute() 17 | { 18 | if ($this->column['type'] === 'array' || $this->column['type'] === 'object') { 19 | return $this->object_array(); 20 | } 21 | $method = $this->column['type']; 22 | 23 | return method_exists($this, $method) ? $this->$method() : $this->missingType(); 24 | } 25 | 26 | protected function smallint() 27 | { 28 | return '$table->smallInteger("'.$this->column['name'].'")'.$this->modifiers().';'; 29 | } 30 | 31 | protected function integer() 32 | { 33 | return '$table->integer("'.$this->column['name'].'")'.$this->modifiers().';'; 34 | } 35 | 36 | protected function float() 37 | { 38 | return '$table->float("'.$this->column['name'].'",'.$this->precisionAndScale().')'.$this->modifiers().';'; 39 | } 40 | 41 | protected function decimal() 42 | { 43 | return '$table->decimal("'.$this->column['name'].'", '.$this->precisionAndScale().')'.$this->modifiers().';'; 44 | } 45 | 46 | protected function bigint() 47 | { 48 | return '$table->bigInteger("'.$this->column['name'].'")'.$this->modifiers().';'; 49 | } 50 | 51 | protected function string(): string 52 | { 53 | return '$table->string("'.$this->column['name'].$this->column['length'].'")'.$this->modifiers().';'; 54 | } 55 | 56 | protected function text(): string 57 | { 58 | return '$table->text("'.$this->column['name'].'")'.$this->modifiers().';'; 59 | } 60 | 61 | protected function enum(): string 62 | { 63 | $distinct = '"'.DB::connection($this->column['connection'])->table($this->column['table'])->select($this->column['name'])->groupBy($this->column['name'])->get()->pluck($this->column['name'])->join('","').'"'; 64 | 65 | return '$table->enum("'.$this->column['name'].'", ['.$distinct.'])'.$this->modifiers().';'; 66 | } 67 | 68 | protected function object_array(): string 69 | { 70 | return '$table->text("'.$this->column['name'].'")'.$this->modifiers().';'; 71 | } 72 | 73 | protected function simple_array(): string 74 | { 75 | return '$table->text("'.$this->column['name'].'")'.$this->modifiers().';'; 76 | } 77 | 78 | protected function json_array(): string 79 | { 80 | return '$table->json("'.$this->column['name'].'")'.$this->modifiers().';'; 81 | } 82 | 83 | protected function json(): string 84 | { 85 | return '$table->json("'.$this->column['name'].'")'.$this->modifiers().';'; 86 | } 87 | 88 | protected function blob(): string 89 | { 90 | return '$table->binary("'.$this->column['name'].'")'.$this->modifiers().';'; 91 | } 92 | 93 | protected function datetime(): string 94 | { 95 | return '$table->timeStamp("'.$this->column['name'].'")'.$this->modifiers().';'; 96 | } 97 | 98 | protected function date() 99 | { 100 | return '$table->date("'.$this->column['name'].'")'.$this->modifiers().';'; 101 | } 102 | 103 | protected function geometry() 104 | { 105 | return '$table->geometry("'.$this->column['name'].'")'.$this->modifiers().';'; 106 | } 107 | 108 | protected function boolean() 109 | { 110 | return '$table->boolean("'.$this->column['name'].'")'.$this->modifiers().';'; 111 | } 112 | 113 | private function modifiers() 114 | { 115 | $string = ''; 116 | $modifiers = [ 117 | 'nullable' => ['smallint', 'integer', 'bigint', 'string', 'text'], 118 | 'unsigned' => ['smallint', 'integer', 'bigint'], 119 | 'useCurrent' => ['datetime'], 120 | 'autoIncrement' => ['smallint', 'integer', 'bigint'], 121 | 'collation' => ['string', 'text'], 122 | 'charset' => ['string', 'text'], 123 | 'default' => ['string', 'text', 'boolean', 'integer', 'smallint', 'bigint', 'float', 'decimal'], 124 | 'comment' => ['*'], 125 | 126 | ]; 127 | 128 | foreach ($modifiers as $k => $v) { 129 | if (in_array($this->column['type'], $v) || in_array('*', $v)) { 130 | $string .= $this->$k(); 131 | } 132 | } 133 | 134 | return $string; 135 | } 136 | 137 | private function length() 138 | { 139 | return is_null($this->column['length']) ? '' : ', '.$this->column['length']; 140 | } 141 | 142 | private function precisionAndScale() 143 | { 144 | return !is_null($this->column['precision'] && !is_null($this->column['scale'])) ? 145 | $this->column['precision'].', '.$this->column['scale'] : ''; 146 | } 147 | 148 | private function nullable() 149 | { 150 | return $this->column['notnull'] ? '' : '->nullable()'; 151 | } 152 | 153 | private function unsigned() 154 | { 155 | return $this->column['unsigned'] ? '->unsigned()' : ''; 156 | } 157 | 158 | private function useCurrent() 159 | { 160 | return $this->column['default'] === 'CURRENT_TIMESTAMP' ? '->useCurrent()' : ''; 161 | } 162 | 163 | private function autoIncrement() 164 | { 165 | return $this->column['autoincrement'] ? '->autoIncrement()' : ''; 166 | } 167 | 168 | private function collation() 169 | { 170 | return isset($this->column['options']['collation']) ? '->collation("'.$this->column['options']['collation'].'")' : ''; 171 | } 172 | 173 | private function charset() 174 | { 175 | return isset($this->column['options']['charset']) ? '->charset("'.$this->column['options']['charset'].'")' : ''; 176 | } 177 | 178 | private function comment() 179 | { 180 | return is_null($this->column['comment']) ? '' : '->comment("'.$this->column['comment'].'")'; 181 | } 182 | 183 | private function default() 184 | { 185 | return is_null($this->column['default']) ? '' : '->default("'.$this->column['default'].'")'; 186 | } 187 | 188 | private function missingType() 189 | { 190 | dd('Missing type '.$this->column['type']); 191 | 192 | return ''; 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /src/Nova.php: -------------------------------------------------------------------------------- 1 | column = $column; 13 | $this->name = strtolower($column['name']); 14 | } 15 | 16 | public function execute() 17 | { 18 | $type = $this->column['type']; 19 | 20 | return method_exists($this, $type) ? $this->$type() : $this->missingTypeMethod(); 21 | } 22 | 23 | public function string() 24 | { 25 | return 'Text::make('.$this->name.')'; 26 | } 27 | 28 | public function simple_array() 29 | { 30 | return 'Code::make('.$this->name.')'; 31 | } 32 | 33 | public function text() 34 | { 35 | return 'Text::make('.$this->name.')'; 36 | } 37 | 38 | public function geometry() 39 | { 40 | return 'Text::make('.$this->name.')'; 41 | } 42 | 43 | public function boolean() 44 | { 45 | return 'Boolean::make('.$this->name.')'; 46 | } 47 | 48 | public function smallint() 49 | { 50 | return 'Number::make('.$this->name.')'; 51 | } 52 | 53 | public function integer() 54 | { 55 | return 'Number::make('.$this->name.')'; 56 | } 57 | 58 | public function bigint() 59 | { 60 | return 'Number::make('.$this->name.')'; 61 | } 62 | 63 | public function blob() 64 | { 65 | return ''; 66 | } 67 | 68 | public function enum() 69 | { 70 | return 'Text::make('.$this->name.')'; 71 | } 72 | 73 | public function datetime() 74 | { 75 | return 'DateTime::make('.$this->name.')'; 76 | } 77 | 78 | public function date() 79 | { 80 | return 'Date::make('.$this->name.')'; 81 | } 82 | 83 | public function float() 84 | { 85 | return 'Number::make('.$this->name.')'; 86 | } 87 | 88 | public function decimal() 89 | { 90 | return 'Number::make('.$this->name.')'; 91 | } 92 | 93 | private function missingTypeMethod() 94 | { 95 | dd('Missing a nova generator for '.$this->column['type']); 96 | 97 | return '$faker->word'; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/Stub.php: -------------------------------------------------------------------------------- 1 | assign = [ 17 | 'php_open' => ' [], 19 | 'attributes' => [], 20 | 'add_soft_deletes' => false, 21 | 'add_timestamps' => false, 22 | 'soft_deletes' => false, 23 | 'timestamps' => false, 24 | 'connection' => '', 25 | 'table' => '', 26 | ]; 27 | } 28 | 29 | public function model($path): string 30 | { 31 | $this->assign['soft_delete'] = isset($this->columns['deleted_at']); 32 | $this->assign['timestamps'] = isset($this->columns['created_at']) && isset($this->columns['updated_at']); 33 | 34 | foreach ($this->columns as $k => $v) { 35 | if ($v['type'] == 'json') { 36 | $this->assign['casts'][$k] = 'array'; 37 | $this->assign['attributes'][$k] = '{}'; 38 | } 39 | } 40 | 41 | return $this->build('model'); 42 | } 43 | 44 | public function migration($path): string 45 | { 46 | $this->assign['class_name'] = 'Create'.Str::studly($this->assign['table']).'Table'; 47 | $created_at = $updated_at = $soft_deletes = false; 48 | foreach ($this->columns as $column) { 49 | if ($column['name'] == 'deleted_at') { 50 | $soft_deletes = true; 51 | } 52 | if ($column['name'] == 'created_at') { 53 | $created_at = true; 54 | } 55 | if ($column['name'] == 'updated_at') { 56 | $updated_at = true; 57 | } 58 | } 59 | 60 | $this->assign['timestamps'] = $updated_at && $created_at; 61 | 62 | return $this->build('migration'); 63 | } 64 | 65 | public function factory($path): string 66 | { 67 | return $this->build('factory'); 68 | } 69 | 70 | public function nova($path): string 71 | { 72 | return $this->build('nova'); 73 | } 74 | 75 | public function setTable($table): void 76 | { 77 | $this->assign['table'] = $table; 78 | } 79 | 80 | public function setConnection($connection): void 81 | { 82 | $this->assign['connection'] = $connection; 83 | } 84 | 85 | public function setModelClassName(string $value) 86 | { 87 | $this->assign['model_name'] = $value; 88 | } 89 | 90 | public function setModelNamespace(string $value) 91 | { 92 | $this->assign['model_namespace'] = $value; 93 | } 94 | 95 | public function setSoftDeletes(bool $value) 96 | { 97 | $this->assign['soft_deletes'] = $value; 98 | } 99 | 100 | public function setColumns(Collection $columns): void 101 | { 102 | $this->columns = $columns; 103 | $this->assign['columns'] = $this->columns; 104 | } 105 | 106 | public function setOptions($options) 107 | { 108 | foreach ($options as $k => $v) { 109 | if (in_array($k, ['updated_at', 'created_at', 'add_timestamps', 'add_deletes'])) { 110 | $this->assign[$k] = $v; 111 | } 112 | } 113 | } 114 | 115 | private function share(): void 116 | { 117 | foreach ($this->assign as $k => $v) { 118 | View::share($k, $v); 119 | } 120 | } 121 | 122 | public function build($component): string 123 | { 124 | $this->share(); 125 | 126 | return view(config('laravelizer.'.$component.'.stub')); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/Types/EnumType.php: -------------------------------------------------------------------------------- 1 | classifier = new Classifier(collect(['Edward', 'Justine', 'Milo'])); 15 | } 16 | 17 | /** @group always */ 18 | public function testClassifier() 19 | { 20 | $this->assertEquals('first_name', $this->classifier->execute()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/DatabaseTest.php: -------------------------------------------------------------------------------- 1 | tables as $table) { 29 | $this->assertContains($table, $database->getTables()); 30 | } 31 | } 32 | 33 | /** 34 | * @test 35 | * @group db 36 | */ 37 | public function can_list_table_column_names() 38 | { 39 | $database = new Database(config('database.default')); 40 | 41 | $this->assertEquals(['actor_id', 'first_name', 'last_name', 'last_update'], $database->getColumnNames('actor')); 42 | } 43 | 44 | /** 45 | * @test 46 | * @group db 47 | */ 48 | public function can_identify_column_types() 49 | { 50 | $database = new Database(config('database.default')); 51 | 52 | $this->assertInstanceOf(Collection::class, $database->getColumns($this->tables[array_rand($this->tables)])); 53 | } 54 | 55 | /** 56 | * @test 57 | * @group db 58 | */ 59 | public function can_get_foreign_key_contraints() 60 | { 61 | $database = new Database(config('database.default')); 62 | 63 | $this->assertInstanceOf(Collection::class, $database->getForeignKeyRestraints($this->tables[array_rand($this->tables)])); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tests/FilesystemTest.php: -------------------------------------------------------------------------------- 1 | assertDirectoryNotExists(config('laravelizer.model.path')); 16 | $write = new Filesystem(); 17 | $write->ensureDirectoryExists(config('laravelizer.model.path')); 18 | $this->assertDirectoryExists(config('laravelizer.model.path')); 19 | } 20 | 21 | /** 22 | * @test 23 | * @group always 24 | */ 25 | public function can_create_directory_from_filename() 26 | { 27 | $this->assertDirectoryNotExists(config('laravelizer.model.path')); 28 | $write = new Filesystem(); 29 | $write->ensureDirectoryExists(config('laravelizer.model.path').'/is_file.txt'); 30 | $this->assertDirectoryExists(config('laravelizer.model.path')); 31 | } 32 | 33 | /** 34 | * @test 35 | * @group always 36 | */ 37 | public function can_write_file() 38 | { 39 | $this->assertFileNotExists(config('laravelizer.model.path').'/is_file.txt'); 40 | $fs = new FileSystem(); 41 | 42 | $fs->write(config('laravelizer.model.path').'/is_file.txt', 'Hello'); 43 | 44 | $this->assertFileExists(config('laravelizer.model.path').'/is_file.txt'); 45 | 46 | $this->assertSame('Hello', $fs->read(config('laravelizer.model.path').'/is_file.txt')); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/StubTest.php: -------------------------------------------------------------------------------- 1 | stub = new Stub(); 22 | $this->stub->setTable('people'); 23 | $this->stub->setModelNamespace('App'); 24 | $this->stub->setColumns(collect(json_decode($this->columns_json, true))); 25 | $this->stub->setModelClassName('Person'); 26 | $this->stub->setConnection('mysql'); 27 | $this->model = $this->stub->model('app\Person.php'); 28 | $this->nova = $this->stub->nova('app\Nova\Person.php'); 29 | $this->factory = $this->stub->factory('database/factories/PersonFactory.php'); 30 | $this->migration = $this->stub->migration('database/migrations/0000_00_00_000000_create_people_table.php'); 31 | } 32 | 33 | /** @group always */ 34 | public function testSetTable() 35 | { 36 | $this->assertSame('people', $this->stub->assign['table']); 37 | } 38 | 39 | /** @group always */ 40 | public function testSetModelNamespace() 41 | { 42 | $this->assertSame('App', $this->stub->assign['model_namespace']); 43 | } 44 | 45 | /** @group always */ 46 | public function testSetColumns() 47 | { 48 | $this->assertEquals(collect(json_decode($this->columns_json, true)), $this->stub->assign['columns']); 49 | } 50 | 51 | /** @group always */ 52 | public function testMigration() 53 | { 54 | $this->assertStringContainsString('Schema::connection(\'mysql\')->create(\'people\', function (Blueprint $table) {', $this->migration); 55 | } 56 | 57 | /** @group always */ 58 | public function testModel() 59 | { 60 | $this->assertStringContainsString('class Person extends Model', $this->model); 61 | $this->assertStringContainsString('namespace App', $this->model); 62 | $this->assertStringContainsString('protected $table = "people"', $this->model); 63 | $this->assertStringContainsString('protected $connection = "mysql"', $this->model); 64 | } 65 | 66 | /** @group always */ 67 | public function testFactory() 68 | { 69 | $this->assertStringContainsString('$factory->define(Person::class, function (Faker $faker) {', $this->factory); 70 | } 71 | 72 | /** @group always */ 73 | public function testNova() 74 | { 75 | $this->assertStringContainsString('public static $model = \'App\Person\';', $this->nova); 76 | } 77 | 78 | /** @group always */ 79 | public function testSetSoftDeletes() 80 | { 81 | $this->stub->setSoftDeletes(0); 82 | $this->assertFalse($this->stub->assign['soft_deletes']); 83 | $this->stub->setSoftDeletes(true); 84 | $this->assertTrue($this->stub->assign['soft_deletes']); 85 | $this->stub->setSoftDeletes(0); 86 | $this->assertFalse($this->stub->assign['soft_deletes']); 87 | $this->stub->setSoftDeletes('yup'); 88 | $this->assertTrue($this->stub->assign['soft_deletes']); 89 | } 90 | 91 | /** @group always */ 92 | public function testSetConnection() 93 | { 94 | $this->assertSame('mysql', $this->stub->assign['connection']); 95 | } 96 | 97 | /** @group always */ 98 | public function testSetModelClassName() 99 | { 100 | $this->assertSame('Person', $this->stub->assign['model_name']); 101 | } 102 | 103 | /** @group always */ 104 | public function testSetOptions() 105 | { 106 | $ts = Carbon::parse('now'); 107 | $this->stub->setOptions(['created_at' => $ts]); 108 | $this->assertSame($ts, $this->stub->assign['created_at']); 109 | $this->stub->setOptions(['nosuchoption' => 'fake']); 110 | $this->assertArrayNotHasKey('nosuchoption', $this->stub->assign); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | afterApplicationCreated(function () { 16 | $this->resetFileSystem(); 17 | }); 18 | $this->beforeApplicationDestroyed(function () { 19 | $this->resetFileSystem(); 20 | }); 21 | 22 | Config::set('database.connections.mysql.database', 'chipperci'); 23 | Config::set('database.connections.mysql.username', 'chipperci'); 24 | Config::set('database.connections.mysql.password', 'secret'); 25 | } 26 | 27 | public function resetFileSystem() 28 | { 29 | foreach (['model', 'migration', 'factory', 'nova', 'test'] as $component) { 30 | Config::set('laravelizer.'.$component.'.path', '/tmp/laravelizer/'.$component); 31 | 32 | $dir = '/tmp/laravelizer/'.$component; 33 | if (is_dir($dir)) { 34 | $files = array_diff(scandir($dir), ['.', '..']); 35 | foreach ($files as $file) { 36 | unlink("$dir/$file"); 37 | } 38 | rmdir($dir); 39 | } 40 | } 41 | } 42 | 43 | protected function getPackageProviders($app) 44 | { 45 | return [ 46 | LaravelizerServiceProvider::class, 47 | ]; 48 | } 49 | 50 | protected function getEnvironmentSetUp($app) 51 | { 52 | $app['config']->set('view.paths', [__DIR__.'/../resources/views']); 53 | $app['config']->set('app.key', 'base64:r0w0xC+mYYqjbZhHZ3uk1oH63VadA3RKrMW52OlIDzI='); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/sakila-db/sakila-schema.sql: -------------------------------------------------------------------------------- 1 | -- chipperci Sample Database Schema 2 | -- Version 1.2 3 | 4 | -- Copyright (c) 2006, 2019, Oracle and/or its affiliates. 5 | -- All rights reserved. 6 | 7 | -- Redistribution and use in source and binary forms, with or without 8 | -- modification, are permitted provided that the following conditions are 9 | -- met: 10 | 11 | -- * Redistributions of source code must retain the above copyright notice, 12 | -- this list of conditions and the following disclaimer. 13 | -- * Redistributions in binary form must reproduce the above copyright 14 | -- notice, this list of conditions and the following disclaimer in the 15 | -- documentation and/or other materials provided with the distribution. 16 | -- * Neither the name of Oracle nor the names of its contributors may be used 17 | -- to endorse or promote products derived from this software without 18 | -- specific prior written permission. 19 | 20 | -- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 21 | -- IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 22 | -- THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 | -- PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 | -- CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 | -- EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 | -- PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 | -- PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 | -- LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 | -- NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | -- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | 32 | SET NAMES utf8mb4; 33 | SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0; 34 | SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0; 35 | SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='TRADITIONAL'; 36 | 37 | DROP SCHEMA IF EXISTS chipperci; 38 | CREATE SCHEMA chipperci; 39 | USE chipperci; 40 | 41 | -- 42 | -- Table structure for table `actor` 43 | -- 44 | 45 | CREATE TABLE actor ( 46 | actor_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT, 47 | first_name VARCHAR(45) NOT NULL, 48 | last_name VARCHAR(45) NOT NULL, 49 | last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 50 | PRIMARY KEY (actor_id), 51 | KEY idx_actor_last_name (last_name) 52 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 53 | 54 | -- 55 | -- Table structure for table `address` 56 | -- 57 | 58 | CREATE TABLE address ( 59 | address_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT, 60 | address VARCHAR(50) NOT NULL, 61 | address2 VARCHAR(50) DEFAULT NULL, 62 | district VARCHAR(20) NOT NULL, 63 | city_id SMALLINT UNSIGNED NOT NULL, 64 | postal_code VARCHAR(10) DEFAULT NULL, 65 | phone VARCHAR(20) NOT NULL, 66 | -- Add GEOMETRY column for MySQL 5.7.5 and higher 67 | -- Also include SRID attribute for MySQL 8.0.3 and higher 68 | /*!50705 location GEOMETRY */ /*!80003 SRID 0 */ /*!50705 NOT NULL,*/ 69 | last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 70 | PRIMARY KEY (address_id), 71 | KEY idx_fk_city_id (city_id), 72 | /*!50705 SPATIAL KEY `idx_location` (location),*/ 73 | CONSTRAINT `fk_address_city` FOREIGN KEY (city_id) REFERENCES city (city_id) ON DELETE RESTRICT ON UPDATE CASCADE 74 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 75 | 76 | -- 77 | -- Table structure for table `category` 78 | -- 79 | 80 | CREATE TABLE category ( 81 | category_id TINYINT UNSIGNED NOT NULL AUTO_INCREMENT, 82 | name VARCHAR(25) NOT NULL, 83 | last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 84 | PRIMARY KEY (category_id) 85 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 86 | 87 | -- 88 | -- Table structure for table `city` 89 | -- 90 | 91 | CREATE TABLE city ( 92 | city_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT, 93 | city VARCHAR(50) NOT NULL, 94 | country_id SMALLINT UNSIGNED NOT NULL, 95 | last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 96 | PRIMARY KEY (city_id), 97 | KEY idx_fk_country_id (country_id), 98 | CONSTRAINT `fk_city_country` FOREIGN KEY (country_id) REFERENCES country (country_id) ON DELETE RESTRICT ON UPDATE CASCADE 99 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 100 | 101 | -- 102 | -- Table structure for table `country` 103 | -- 104 | 105 | CREATE TABLE country ( 106 | country_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT, 107 | country VARCHAR(50) NOT NULL, 108 | last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 109 | PRIMARY KEY (country_id) 110 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 111 | 112 | -- 113 | -- Table structure for table `customer` 114 | -- 115 | 116 | CREATE TABLE customer ( 117 | customer_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT, 118 | store_id TINYINT UNSIGNED NOT NULL, 119 | first_name VARCHAR(45) NOT NULL, 120 | last_name VARCHAR(45) NOT NULL, 121 | email VARCHAR(50) DEFAULT NULL, 122 | address_id SMALLINT UNSIGNED NOT NULL, 123 | active BOOLEAN NOT NULL DEFAULT TRUE, 124 | create_date DATETIME NOT NULL, 125 | last_update TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 126 | PRIMARY KEY (customer_id), 127 | KEY idx_fk_store_id (store_id), 128 | KEY idx_fk_address_id (address_id), 129 | KEY idx_last_name (last_name), 130 | CONSTRAINT fk_customer_address FOREIGN KEY (address_id) REFERENCES address (address_id) ON DELETE RESTRICT ON UPDATE CASCADE, 131 | CONSTRAINT fk_customer_store FOREIGN KEY (store_id) REFERENCES store (store_id) ON DELETE RESTRICT ON UPDATE CASCADE 132 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 133 | 134 | -- 135 | -- Table structure for table `film` 136 | -- 137 | 138 | CREATE TABLE film ( 139 | film_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT, 140 | title VARCHAR(128) NOT NULL, 141 | description TEXT DEFAULT NULL, 142 | release_year YEAR DEFAULT NULL, 143 | language_id TINYINT UNSIGNED NOT NULL, 144 | original_language_id TINYINT UNSIGNED DEFAULT NULL, 145 | rental_duration TINYINT UNSIGNED NOT NULL DEFAULT 3, 146 | rental_rate DECIMAL(4,2) NOT NULL DEFAULT 4.99, 147 | length SMALLINT UNSIGNED DEFAULT NULL, 148 | replacement_cost DECIMAL(5,2) NOT NULL DEFAULT 19.99, 149 | rating ENUM('G','PG','PG-13','R','NC-17') DEFAULT 'G', 150 | special_features SET('Trailers','Commentaries','Deleted Scenes','Behind the Scenes') DEFAULT NULL, 151 | last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 152 | PRIMARY KEY (film_id), 153 | KEY idx_title (title), 154 | KEY idx_fk_language_id (language_id), 155 | KEY idx_fk_original_language_id (original_language_id), 156 | CONSTRAINT fk_film_language FOREIGN KEY (language_id) REFERENCES language (language_id) ON DELETE RESTRICT ON UPDATE CASCADE, 157 | CONSTRAINT fk_film_language_original FOREIGN KEY (original_language_id) REFERENCES language (language_id) ON DELETE RESTRICT ON UPDATE CASCADE 158 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 159 | 160 | -- 161 | -- Table structure for table `film_actor` 162 | -- 163 | 164 | CREATE TABLE film_actor ( 165 | actor_id SMALLINT UNSIGNED NOT NULL, 166 | film_id SMALLINT UNSIGNED NOT NULL, 167 | last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 168 | PRIMARY KEY (actor_id,film_id), 169 | KEY idx_fk_film_id (`film_id`), 170 | CONSTRAINT fk_film_actor_actor FOREIGN KEY (actor_id) REFERENCES actor (actor_id) ON DELETE RESTRICT ON UPDATE CASCADE, 171 | CONSTRAINT fk_film_actor_film FOREIGN KEY (film_id) REFERENCES film (film_id) ON DELETE RESTRICT ON UPDATE CASCADE 172 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 173 | 174 | -- 175 | -- Table structure for table `film_category` 176 | -- 177 | 178 | CREATE TABLE film_category ( 179 | film_id SMALLINT UNSIGNED NOT NULL, 180 | category_id TINYINT UNSIGNED NOT NULL, 181 | last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 182 | PRIMARY KEY (film_id, category_id), 183 | CONSTRAINT fk_film_category_film FOREIGN KEY (film_id) REFERENCES film (film_id) ON DELETE RESTRICT ON UPDATE CASCADE, 184 | CONSTRAINT fk_film_category_category FOREIGN KEY (category_id) REFERENCES category (category_id) ON DELETE RESTRICT ON UPDATE CASCADE 185 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 186 | 187 | -- 188 | -- Table structure for table `film_text` 189 | -- 190 | -- InnoDB added FULLTEXT support in 5.6.10. If you use an 191 | -- earlier version, then consider upgrading (recommended) or 192 | -- changing InnoDB to MyISAM as the film_text engine 193 | -- 194 | 195 | -- Use InnoDB for film_text as of 5.6.10, MyISAM prior to 5.6.10. 196 | SET @old_default_storage_engine = @@default_storage_engine; 197 | SET @@default_storage_engine = 'MyISAM'; 198 | /*!50610 SET @@default_storage_engine = 'InnoDB'*/; 199 | 200 | CREATE TABLE film_text ( 201 | film_id SMALLINT NOT NULL, 202 | title VARCHAR(255) NOT NULL, 203 | description TEXT, 204 | PRIMARY KEY (film_id), 205 | FULLTEXT KEY idx_title_description (title,description) 206 | ) DEFAULT CHARSET=utf8mb4; 207 | 208 | SET @@default_storage_engine = @old_default_storage_engine; 209 | 210 | -- 211 | -- Triggers for loading film_text from film 212 | -- 213 | 214 | DELIMITER ;; 215 | CREATE TRIGGER `ins_film` AFTER INSERT ON `film` FOR EACH ROW BEGIN 216 | INSERT INTO film_text (film_id, title, description) 217 | VALUES (new.film_id, new.title, new.description); 218 | END;; 219 | 220 | 221 | CREATE TRIGGER `upd_film` AFTER UPDATE ON `film` FOR EACH ROW BEGIN 222 | IF (old.title != new.title) OR (old.description != new.description) OR (old.film_id != new.film_id) 223 | THEN 224 | UPDATE film_text 225 | SET title=new.title, 226 | description=new.description, 227 | film_id=new.film_id 228 | WHERE film_id=old.film_id; 229 | END IF; 230 | END;; 231 | 232 | 233 | CREATE TRIGGER `del_film` AFTER DELETE ON `film` FOR EACH ROW BEGIN 234 | DELETE FROM film_text WHERE film_id = old.film_id; 235 | END;; 236 | 237 | DELIMITER ; 238 | 239 | -- 240 | -- Table structure for table `inventory` 241 | -- 242 | 243 | CREATE TABLE inventory ( 244 | inventory_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT, 245 | film_id SMALLINT UNSIGNED NOT NULL, 246 | store_id TINYINT UNSIGNED NOT NULL, 247 | last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 248 | PRIMARY KEY (inventory_id), 249 | KEY idx_fk_film_id (film_id), 250 | KEY idx_store_id_film_id (store_id,film_id), 251 | CONSTRAINT fk_inventory_store FOREIGN KEY (store_id) REFERENCES store (store_id) ON DELETE RESTRICT ON UPDATE CASCADE, 252 | CONSTRAINT fk_inventory_film FOREIGN KEY (film_id) REFERENCES film (film_id) ON DELETE RESTRICT ON UPDATE CASCADE 253 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 254 | 255 | -- 256 | -- Table structure for table `language` 257 | -- 258 | 259 | CREATE TABLE language ( 260 | language_id TINYINT UNSIGNED NOT NULL AUTO_INCREMENT, 261 | name CHAR(20) NOT NULL, 262 | last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 263 | PRIMARY KEY (language_id) 264 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 265 | 266 | -- 267 | -- Table structure for table `payment` 268 | -- 269 | 270 | CREATE TABLE payment ( 271 | payment_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT, 272 | customer_id SMALLINT UNSIGNED NOT NULL, 273 | staff_id TINYINT UNSIGNED NOT NULL, 274 | rental_id INT DEFAULT NULL, 275 | amount DECIMAL(5,2) NOT NULL, 276 | payment_date DATETIME NOT NULL, 277 | last_update TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 278 | PRIMARY KEY (payment_id), 279 | KEY idx_fk_staff_id (staff_id), 280 | KEY idx_fk_customer_id (customer_id), 281 | CONSTRAINT fk_payment_rental FOREIGN KEY (rental_id) REFERENCES rental (rental_id) ON DELETE SET NULL ON UPDATE CASCADE, 282 | CONSTRAINT fk_payment_customer FOREIGN KEY (customer_id) REFERENCES customer (customer_id) ON DELETE RESTRICT ON UPDATE CASCADE, 283 | CONSTRAINT fk_payment_staff FOREIGN KEY (staff_id) REFERENCES staff (staff_id) ON DELETE RESTRICT ON UPDATE CASCADE 284 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 285 | 286 | 287 | -- 288 | -- Table structure for table `rental` 289 | -- 290 | 291 | CREATE TABLE rental ( 292 | rental_id INT NOT NULL AUTO_INCREMENT, 293 | rental_date DATETIME NOT NULL, 294 | inventory_id MEDIUMINT UNSIGNED NOT NULL, 295 | customer_id SMALLINT UNSIGNED NOT NULL, 296 | return_date DATETIME DEFAULT NULL, 297 | staff_id TINYINT UNSIGNED NOT NULL, 298 | last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 299 | PRIMARY KEY (rental_id), 300 | UNIQUE KEY (rental_date,inventory_id,customer_id), 301 | KEY idx_fk_inventory_id (inventory_id), 302 | KEY idx_fk_customer_id (customer_id), 303 | KEY idx_fk_staff_id (staff_id), 304 | CONSTRAINT fk_rental_staff FOREIGN KEY (staff_id) REFERENCES staff (staff_id) ON DELETE RESTRICT ON UPDATE CASCADE, 305 | CONSTRAINT fk_rental_inventory FOREIGN KEY (inventory_id) REFERENCES inventory (inventory_id) ON DELETE RESTRICT ON UPDATE CASCADE, 306 | CONSTRAINT fk_rental_customer FOREIGN KEY (customer_id) REFERENCES customer (customer_id) ON DELETE RESTRICT ON UPDATE CASCADE 307 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 308 | 309 | -- 310 | -- Table structure for table `staff` 311 | -- 312 | 313 | CREATE TABLE staff ( 314 | staff_id TINYINT UNSIGNED NOT NULL AUTO_INCREMENT, 315 | first_name VARCHAR(45) NOT NULL, 316 | last_name VARCHAR(45) NOT NULL, 317 | address_id SMALLINT UNSIGNED NOT NULL, 318 | picture BLOB DEFAULT NULL, 319 | email VARCHAR(50) DEFAULT NULL, 320 | store_id TINYINT UNSIGNED NOT NULL, 321 | active BOOLEAN NOT NULL DEFAULT TRUE, 322 | username VARCHAR(16) NOT NULL, 323 | password VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, 324 | last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 325 | PRIMARY KEY (staff_id), 326 | KEY idx_fk_store_id (store_id), 327 | KEY idx_fk_address_id (address_id), 328 | CONSTRAINT fk_staff_store FOREIGN KEY (store_id) REFERENCES store (store_id) ON DELETE RESTRICT ON UPDATE CASCADE, 329 | CONSTRAINT fk_staff_address FOREIGN KEY (address_id) REFERENCES address (address_id) ON DELETE RESTRICT ON UPDATE CASCADE 330 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 331 | 332 | -- 333 | -- Table structure for table `store` 334 | -- 335 | 336 | CREATE TABLE store ( 337 | store_id TINYINT UNSIGNED NOT NULL AUTO_INCREMENT, 338 | manager_staff_id TINYINT UNSIGNED NOT NULL, 339 | address_id SMALLINT UNSIGNED NOT NULL, 340 | last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 341 | PRIMARY KEY (store_id), 342 | UNIQUE KEY idx_unique_manager (manager_staff_id), 343 | KEY idx_fk_address_id (address_id), 344 | CONSTRAINT fk_store_staff FOREIGN KEY (manager_staff_id) REFERENCES staff (staff_id) ON DELETE RESTRICT ON UPDATE CASCADE, 345 | CONSTRAINT fk_store_address FOREIGN KEY (address_id) REFERENCES address (address_id) ON DELETE RESTRICT ON UPDATE CASCADE 346 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 347 | 348 | -- 349 | -- View structure for view `customer_list` 350 | -- 351 | 352 | CREATE VIEW customer_list 353 | AS 354 | SELECT cu.customer_id AS ID, CONCAT(cu.first_name, _utf8mb4' ', cu.last_name) AS name, a.address AS address, a.postal_code AS `zip code`, 355 | a.phone AS phone, city.city AS city, country.country AS country, IF(cu.active, _utf8mb4'active',_utf8mb4'') AS notes, cu.store_id AS SID 356 | FROM customer AS cu JOIN address AS a ON cu.address_id = a.address_id JOIN city ON a.city_id = city.city_id 357 | JOIN country ON city.country_id = country.country_id; 358 | 359 | -- 360 | -- View structure for view `film_list` 361 | -- 362 | 363 | CREATE VIEW film_list 364 | AS 365 | SELECT film.film_id AS FID, film.title AS title, film.description AS description, category.name AS category, film.rental_rate AS price, 366 | film.length AS length, film.rating AS rating, GROUP_CONCAT(CONCAT(actor.first_name, _utf8mb4' ', actor.last_name) SEPARATOR ', ') AS actors 367 | FROM category LEFT JOIN film_category ON category.category_id = film_category.category_id LEFT JOIN film ON film_category.film_id = film.film_id 368 | JOIN film_actor ON film.film_id = film_actor.film_id 369 | JOIN actor ON film_actor.actor_id = actor.actor_id 370 | GROUP BY film.film_id, category.name; 371 | 372 | -- 373 | -- View structure for view `nicer_but_slower_film_list` 374 | -- 375 | 376 | CREATE VIEW nicer_but_slower_film_list 377 | AS 378 | SELECT film.film_id AS FID, film.title AS title, film.description AS description, category.name AS category, film.rental_rate AS price, 379 | film.length AS length, film.rating AS rating, GROUP_CONCAT(CONCAT(CONCAT(UCASE(SUBSTR(actor.first_name,1,1)), 380 | LCASE(SUBSTR(actor.first_name,2,LENGTH(actor.first_name))),_utf8mb4' ',CONCAT(UCASE(SUBSTR(actor.last_name,1,1)), 381 | LCASE(SUBSTR(actor.last_name,2,LENGTH(actor.last_name)))))) SEPARATOR ', ') AS actors 382 | FROM category LEFT JOIN film_category ON category.category_id = film_category.category_id LEFT JOIN film ON film_category.film_id = film.film_id 383 | JOIN film_actor ON film.film_id = film_actor.film_id 384 | JOIN actor ON film_actor.actor_id = actor.actor_id 385 | GROUP BY film.film_id, category.name; 386 | 387 | -- 388 | -- View structure for view `staff_list` 389 | -- 390 | 391 | CREATE VIEW staff_list 392 | AS 393 | SELECT s.staff_id AS ID, CONCAT(s.first_name, _utf8mb4' ', s.last_name) AS name, a.address AS address, a.postal_code AS `zip code`, a.phone AS phone, 394 | city.city AS city, country.country AS country, s.store_id AS SID 395 | FROM staff AS s JOIN address AS a ON s.address_id = a.address_id JOIN city ON a.city_id = city.city_id 396 | JOIN country ON city.country_id = country.country_id; 397 | 398 | -- 399 | -- View structure for view `sales_by_store` 400 | -- 401 | 402 | CREATE VIEW sales_by_store 403 | AS 404 | SELECT 405 | CONCAT(c.city, _utf8mb4',', cy.country) AS store 406 | , CONCAT(m.first_name, _utf8mb4' ', m.last_name) AS manager 407 | , SUM(p.amount) AS total_sales 408 | FROM payment AS p 409 | INNER JOIN rental AS r ON p.rental_id = r.rental_id 410 | INNER JOIN inventory AS i ON r.inventory_id = i.inventory_id 411 | INNER JOIN store AS s ON i.store_id = s.store_id 412 | INNER JOIN address AS a ON s.address_id = a.address_id 413 | INNER JOIN city AS c ON a.city_id = c.city_id 414 | INNER JOIN country AS cy ON c.country_id = cy.country_id 415 | INNER JOIN staff AS m ON s.manager_staff_id = m.staff_id 416 | GROUP BY s.store_id 417 | ORDER BY cy.country, c.city; 418 | 419 | -- 420 | -- View structure for view `sales_by_film_category` 421 | -- 422 | -- Note that total sales will add up to >100% because 423 | -- some titles belong to more than 1 category 424 | -- 425 | 426 | CREATE VIEW sales_by_film_category 427 | AS 428 | SELECT 429 | c.name AS category 430 | , SUM(p.amount) AS total_sales 431 | FROM payment AS p 432 | INNER JOIN rental AS r ON p.rental_id = r.rental_id 433 | INNER JOIN inventory AS i ON r.inventory_id = i.inventory_id 434 | INNER JOIN film AS f ON i.film_id = f.film_id 435 | INNER JOIN film_category AS fc ON f.film_id = fc.film_id 436 | INNER JOIN category AS c ON fc.category_id = c.category_id 437 | GROUP BY c.name 438 | ORDER BY total_sales DESC; 439 | 440 | -- 441 | -- View structure for view `actor_info` 442 | -- 443 | 444 | CREATE DEFINER=CURRENT_USER SQL SECURITY INVOKER VIEW actor_info 445 | AS 446 | SELECT 447 | a.actor_id, 448 | a.first_name, 449 | a.last_name, 450 | GROUP_CONCAT(DISTINCT CONCAT(c.name, ': ', 451 | (SELECT GROUP_CONCAT(f.title ORDER BY f.title SEPARATOR ', ') 452 | FROM chipperci.film f 453 | INNER JOIN chipperci.film_category fc 454 | ON f.film_id = fc.film_id 455 | INNER JOIN chipperci.film_actor fa 456 | ON f.film_id = fa.film_id 457 | WHERE fc.category_id = c.category_id 458 | AND fa.actor_id = a.actor_id 459 | ) 460 | ) 461 | ORDER BY c.name SEPARATOR '; ') 462 | AS film_info 463 | FROM chipperci.actor a 464 | LEFT JOIN chipperci.film_actor fa 465 | ON a.actor_id = fa.actor_id 466 | LEFT JOIN chipperci.film_category fc 467 | ON fa.film_id = fc.film_id 468 | LEFT JOIN chipperci.category c 469 | ON fc.category_id = c.category_id 470 | GROUP BY a.actor_id, a.first_name, a.last_name; 471 | 472 | -- 473 | -- Procedure structure for procedure `rewards_report` 474 | -- 475 | 476 | DELIMITER // 477 | 478 | CREATE PROCEDURE rewards_report ( 479 | IN min_monthly_purchases TINYINT UNSIGNED 480 | , IN min_dollar_amount_purchased DECIMAL(10,2) 481 | , OUT count_rewardees INT 482 | ) 483 | LANGUAGE SQL 484 | NOT DETERMINISTIC 485 | READS SQL DATA 486 | SQL SECURITY DEFINER 487 | COMMENT 'Provides a customizable report on best customers' 488 | proc: BEGIN 489 | 490 | DECLARE last_month_start DATE; 491 | DECLARE last_month_end DATE; 492 | 493 | /* Some sanity checks... */ 494 | IF min_monthly_purchases = 0 THEN 495 | SELECT 'Minimum monthly purchases parameter must be > 0'; 496 | LEAVE proc; 497 | END IF; 498 | IF min_dollar_amount_purchased = 0.00 THEN 499 | SELECT 'Minimum monthly dollar amount purchased parameter must be > $0.00'; 500 | LEAVE proc; 501 | END IF; 502 | 503 | /* Determine start and end time periods */ 504 | SET last_month_start = DATE_SUB(CURRENT_DATE(), INTERVAL 1 MONTH); 505 | SET last_month_start = STR_TO_DATE(CONCAT(YEAR(last_month_start),'-',MONTH(last_month_start),'-01'),'%Y-%m-%d'); 506 | SET last_month_end = LAST_DAY(last_month_start); 507 | 508 | /* 509 | Create a temporary storage area for 510 | Customer IDs. 511 | */ 512 | CREATE TEMPORARY TABLE tmpCustomer (customer_id SMALLINT UNSIGNED NOT NULL PRIMARY KEY); 513 | 514 | /* 515 | Find all customers meeting the 516 | monthly purchase requirements 517 | */ 518 | INSERT INTO tmpCustomer (customer_id) 519 | SELECT p.customer_id 520 | FROM payment AS p 521 | WHERE DATE(p.payment_date) BETWEEN last_month_start AND last_month_end 522 | GROUP BY customer_id 523 | HAVING SUM(p.amount) > min_dollar_amount_purchased 524 | AND COUNT(customer_id) > min_monthly_purchases; 525 | 526 | /* Populate OUT parameter with count of found customers */ 527 | SELECT COUNT(*) FROM tmpCustomer INTO count_rewardees; 528 | 529 | /* 530 | Output ALL customer information of matching rewardees. 531 | Customize output as needed. 532 | */ 533 | SELECT c.* 534 | FROM tmpCustomer AS t 535 | INNER JOIN customer AS c ON t.customer_id = c.customer_id; 536 | 537 | /* Clean up */ 538 | DROP TABLE tmpCustomer; 539 | END // 540 | 541 | DELIMITER ; 542 | 543 | DELIMITER $$ 544 | 545 | CREATE FUNCTION get_customer_balance(p_customer_id INT, p_effective_date DATETIME) RETURNS DECIMAL(5,2) 546 | DETERMINISTIC 547 | READS SQL DATA 548 | BEGIN 549 | 550 | #OK, WE NEED TO CALCULATE THE CURRENT BALANCE GIVEN A CUSTOMER_ID AND A DATE 551 | #THAT WE WANT THE BALANCE TO BE EFFECTIVE FOR. THE BALANCE IS: 552 | # 1) RENTAL FEES FOR ALL PREVIOUS RENTALS 553 | # 2) ONE DOLLAR FOR EVERY DAY THE PREVIOUS RENTALS ARE OVERDUE 554 | # 3) IF A FILM IS MORE THAN RENTAL_DURATION * 2 OVERDUE, CHARGE THE REPLACEMENT_COST 555 | # 4) SUBTRACT ALL PAYMENTS MADE BEFORE THE DATE SPECIFIED 556 | 557 | DECLARE v_rentfees DECIMAL(5,2); #FEES PAID TO RENT THE VIDEOS INITIALLY 558 | DECLARE v_overfees INTEGER; #LATE FEES FOR PRIOR RENTALS 559 | DECLARE v_payments DECIMAL(5,2); #SUM OF PAYMENTS MADE PREVIOUSLY 560 | 561 | SELECT IFNULL(SUM(film.rental_rate),0) INTO v_rentfees 562 | FROM film, inventory, rental 563 | WHERE film.film_id = inventory.film_id 564 | AND inventory.inventory_id = rental.inventory_id 565 | AND rental.rental_date <= p_effective_date 566 | AND rental.customer_id = p_customer_id; 567 | 568 | SELECT IFNULL(SUM(IF((TO_DAYS(rental.return_date) - TO_DAYS(rental.rental_date)) > film.rental_duration, 569 | ((TO_DAYS(rental.return_date) - TO_DAYS(rental.rental_date)) - film.rental_duration),0)),0) INTO v_overfees 570 | FROM rental, inventory, film 571 | WHERE film.film_id = inventory.film_id 572 | AND inventory.inventory_id = rental.inventory_id 573 | AND rental.rental_date <= p_effective_date 574 | AND rental.customer_id = p_customer_id; 575 | 576 | 577 | SELECT IFNULL(SUM(payment.amount),0) INTO v_payments 578 | FROM payment 579 | 580 | WHERE payment.payment_date <= p_effective_date 581 | AND payment.customer_id = p_customer_id; 582 | 583 | RETURN v_rentfees + v_overfees - v_payments; 584 | END $$ 585 | 586 | DELIMITER ; 587 | 588 | DELIMITER $$ 589 | 590 | CREATE PROCEDURE film_in_stock(IN p_film_id INT, IN p_store_id INT, OUT p_film_count INT) 591 | READS SQL DATA 592 | BEGIN 593 | SELECT inventory_id 594 | FROM inventory 595 | WHERE film_id = p_film_id 596 | AND store_id = p_store_id 597 | AND inventory_in_stock(inventory_id); 598 | 599 | SELECT COUNT(*) 600 | FROM inventory 601 | WHERE film_id = p_film_id 602 | AND store_id = p_store_id 603 | AND inventory_in_stock(inventory_id) 604 | INTO p_film_count; 605 | END $$ 606 | 607 | DELIMITER ; 608 | 609 | DELIMITER $$ 610 | 611 | CREATE PROCEDURE film_not_in_stock(IN p_film_id INT, IN p_store_id INT, OUT p_film_count INT) 612 | READS SQL DATA 613 | BEGIN 614 | SELECT inventory_id 615 | FROM inventory 616 | WHERE film_id = p_film_id 617 | AND store_id = p_store_id 618 | AND NOT inventory_in_stock(inventory_id); 619 | 620 | SELECT COUNT(*) 621 | FROM inventory 622 | WHERE film_id = p_film_id 623 | AND store_id = p_store_id 624 | AND NOT inventory_in_stock(inventory_id) 625 | INTO p_film_count; 626 | END $$ 627 | 628 | DELIMITER ; 629 | 630 | DELIMITER $$ 631 | 632 | CREATE FUNCTION inventory_held_by_customer(p_inventory_id INT) RETURNS INT 633 | READS SQL DATA 634 | BEGIN 635 | DECLARE v_customer_id INT; 636 | DECLARE EXIT HANDLER FOR NOT FOUND RETURN NULL; 637 | 638 | SELECT customer_id INTO v_customer_id 639 | FROM rental 640 | WHERE return_date IS NULL 641 | AND inventory_id = p_inventory_id; 642 | 643 | RETURN v_customer_id; 644 | END $$ 645 | 646 | DELIMITER ; 647 | 648 | DELIMITER $$ 649 | 650 | CREATE FUNCTION inventory_in_stock(p_inventory_id INT) RETURNS BOOLEAN 651 | READS SQL DATA 652 | BEGIN 653 | DECLARE v_rentals INT; 654 | DECLARE v_out INT; 655 | 656 | #AN ITEM IS IN-STOCK IF THERE ARE EITHER NO ROWS IN THE rental TABLE 657 | #FOR THE ITEM OR ALL ROWS HAVE return_date POPULATED 658 | 659 | SELECT COUNT(*) INTO v_rentals 660 | FROM rental 661 | WHERE inventory_id = p_inventory_id; 662 | 663 | IF v_rentals = 0 THEN 664 | RETURN TRUE; 665 | END IF; 666 | 667 | SELECT COUNT(rental_id) INTO v_out 668 | FROM inventory LEFT JOIN rental USING(inventory_id) 669 | WHERE inventory.inventory_id = p_inventory_id 670 | AND rental.return_date IS NULL; 671 | 672 | IF v_out > 0 THEN 673 | RETURN FALSE; 674 | ELSE 675 | RETURN TRUE; 676 | END IF; 677 | END $$ 678 | 679 | DELIMITER ; 680 | 681 | SET SQL_MODE=@OLD_SQL_MODE; 682 | SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS; 683 | SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS; 684 | 685 | 686 | -------------------------------------------------------------------------------- /tests/sakila-db/sakila.mwb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grosv/laravelizer/24e3dd6e726d2d3bba68ba8a2ee60c064e4450ad/tests/sakila-db/sakila.mwb --------------------------------------------------------------------------------