module DBML::Parser
Constants
- ATOM
- BOOLEAN
ATOM
parses true: 'true' => trueATOM
parses false: 'false' => falseATOM
parses null: 'null' => nilATOM
parses numbers: '123.45678' => 123.45678ATOM
parses strings: “'string'” => “string”ATOM
parses multilines: “'''longnstring'''” => “longnstring”ATOM
parses expressions: '`now()`' => DBML::Expression.new('now()')- COLUMN
- COLUMN_NAME
Column Definition
¶ ↑
-
name of the column is listed as column_name
-
type of the data in the column listed as column_type
-
supports all data types, as long as it is a single word (remove all spaces in the data type). Example, JSON, JSONB, decimal(1,2), etc.
-
column_name can be stated in just plain text, or wrapped in a double quote as “column name”
Column Settings
Each column can take have optional settings, defined in square brackets like:
Table buildings { //... address varchar(255) [unique, not null, note: 'to include unit number'] id integer [ pk, unique, default: 123, note: 'Number' ] }
COLUMN
parses naked identifiers as names: 'column_name type' => DBML::Column.new('column_name', 'type', {})COLUMN
parses quoted identifiers as names: '“column name” type' => DBML::Column.new('column name', 'type', {})COLUMN
parses types: 'name string' => DBML::Column.new('name', 'string', {})COLUMN
parses settings: 'name string [pk]' => DBML::Column.new('name', 'string', {pk: nil})-
- COLUMN_TYPE
- COMPOSITE_COLUMNS
Relationships & Foreign Key Definitions
Relationships are used to define foreign key constraints between tables.
Table posts { id integer [primary key] user_id integer [ref: > users.id] // many-to-one } // or this Table users { id integer [ref: < posts.user_id, ref: < reviews.user_id] // one to many } // The space after '<' is optional
There are 3 types of relationships: one-to-one, one-to-many, and many-to-one
1. <: one-to-many. E.g: users.id < posts.user_id 2. >: many-to-one. E.g: posts.user_id > users.id 3. -: one-to-one. E.g: users.id - user_infos.user_id
Composite foreign keys:
Ref: merchant_periods.(merchant_id, country_code) > merchants.(id, country_code)
In
DBML
, there are 3 syntaxes to define relationships://Long form Ref name_optional { table1.column1 < table2.column2 } //Short form: Ref name_optional: table1.column1 < table2.column2 // Inline form Table posts { id integer user_id integer [ref: > users.id] }
Relationship settings
Ref: products.merchant_id > merchants.id [delete: cascade, update: no action]
-
delete / update: cascade | restrict | set null | set default | no action Define referential actions. Similar to ON DELETE/UPDATE CASCADE/… in SQL.
Relationship settings are not supported for inline form ref.
COMPOSITE_COLUMNS
parses single column: '(column)' => ['column']COMPOSITE_COLUMNS
parses multiple columns: '(col1, col2)' => ['col1', 'col2']RELATIONSHIP_PART
parses simple form: 'table.column' => ['table', ['column']]RELATIONSHIP_PART
parses composite form: 'table.(a, b)' => ['table', ['a', 'b']]RELATIONSHIP
parses long form: “Ref name {nleft.lcol < right.rcoln}” => DBML::Relationship.new('name', 'left', ['lcol'], '<', 'right', ['rcol'], {})RELATIONSHIP
parses short form: “Ref name: left.lcol > right.rcol” => DBML::Relationship.new('name', 'left', ['lcol'], '>', 'right', ['rcol'], {})RELATIONSHIP
parses composite form: 'Ref: left.(a, b) - right.(c, d)' => DBML::Relationship.new(nil, 'left', ['a', 'b'], '-', 'right', ['c', 'd'], {})RELATIONSHIP
parses lowercase r: “ref name: left.lcol > right.rcol” => DBML::Relationship.new('name', 'left', ['lcol'], '>', 'right', ['rcol'], {})RELATIONSHIP
parses settings: “Ref: L.a > R.b [delete: cascade, update: no action]” => DBML::Relationship.new(nil, 'L', ['a'], '>', 'R', ['b'], {delete: :cascade, update: :'no action'})-
- ENUM
- ENUM_CHOICE
Enum Definition
Enum allows users to define different values of a particular column.
enum job_status { created [note: 'Waiting to be processed'] running done failure }
ENUM
parses empty blocks: “enum empty {n}” => DBML::Enum.new('empty', [])ENUM
parses settings: “enum setting {none [note: 'something']n}” => DBML::Enum.new('setting', [DBML::EnumChoice.new('one', {note: 'something'})])ENUM
parses filled blocks: “enum filled {nonentwo}” => DBML::Enum.new('filled', [DBML::EnumChoice.new('one', {}), DBML::EnumChoice.new('two', {})])- EXPRESSION
- IDENTIFIER
- INDEX
- INDEXES
- INDEX_COMPOSITE
- INDEX_SINGLE
Index Definition
Indexes allow users to quickly locate and access the data. Users can define single or multi-column indexes.
Table bookings { id integer country varchar booking_date date created_at timestamp indexes { (id, country) [pk] // composite primary key created_at [note: 'Date'] booking_date (country, booking_date) [unique] booking_date [type: hash] (`id*2`) (`id*3`,`getdate()`) (`id*3`,id) } }
There are 3 types of index definitions:
# Index with single field (with index name): CREATE
INDEX
on users (created_at) # Index with multiple fields (composite index): CREATEINDEX
on users (created_at, country) # Index with an expression: CREATEINDEX
ON films ( first_name + last_name ) # (bonus) Composite index with expression: CREATEINDEX
ON users ( country, (lower(name)) )INDEX
parses single fields: 'id' => DBML::Index.new(, {})INDEX
parses composite fields: '(id, country)' => DBML::Index.new(['id', 'country'], {})INDEX
parses expressions: '(`id*2`)' => :Index.new(, {})INDEX
parses expressions: '(`id*2`,`id*3`)' => DBML::Index.new([DBML::Expression.new('id*2'), DBML::Expression.new('id*3')], {})INDEX
parses naked ids and settings: “test_col [type: hash]” => DBML::Index.new(, {type: :hash})INDEX
parses settings: '(country, booking_date) [unique]' => DBML::Index.new(['country', 'booking_date'], {unique: nil})INDEXES
parses empty block: 'indexes { }' => []INDEXES
parses single index: “indexes {ncolumn_namen}” => [DBML::Index.new(, {})]INDEXES
parses multiple indexes: “indexes {n(composite) [pk]ntest_index [unique]n}” => [DBML::Index.new(, {pk: nil}), DBML::Index.new(, {unique: nil})]- KEYWORD
KEYWORD
parses phrases: 'no action' => :“no action”- MULTI_LINE_STRING
MULTI_LINE_STRING
ignores indentation on the first line: “''' longn string'''” => “longn string”MULTI_LINE_STRING
allows apostrophes: “'''it's a string with '' bunny ears'''” => “it's a string with '' bunny ears”MULTI_LINE_STRING
allows blanks: “''''''” => “”MULTI_LINE_STRING
allows blank lines to have no indent: “''' my stringnn it's really great'''” => “my stringnit's really great”MULTI_LINE_STRING
allows blank first lines: “'''n start ofn my writing'''” => “start ofnmy writing”- NAKED_IDENTIFIER
- NOTE
NOTE
parses short notes: “Note: 'this is cool'” => 'this is cool'NOTE
parses block notes: “Note {n'still a single line of note'n}” => 'still a single line of note'NOTE
can use multilines: “Note: '''this isnnot reassuring'''” => “this isnnot reassuring”- NULL
- NUMBER
- PROJECT
PROJECT
can be empty: “” => DBML::Project.new(nil, [], {}, [], [], [], [])PROJECT
includes definition info: “Project p { Note: 'hello' }” => DBML::Project.new('p', ['hello'], {}, [], [], [], [])PROJECT
includes tables: “Table t { }” => DBML::Project.new(nil, [], {}, [DBML::Table.new('t', nil, [], [], [])], [], [], [])PROJECT
includes enums: “enum E { }” => DBML::Project.new(nil, [], {}, [], [], [DBML::Enum.new('E', [])], [])PROJECT
includes table groups: “TableGroup TG { }” => DBML::Project.new(nil, [], {}, [], [], [], [DBML::TableGroup.new('TG', [])])- PROJECT_DEFINITION
Project Definition
¶ ↑
You can give overall description of the project.
Project project_name { database_type: 'PostgreSQL' Note: 'Description of the project' }
PROJECT_DEFINITION
parses names: 'Project my_proj { }' => DBML::ProjectDef.new('my_proj', [], {})PROJECT_DEFINITION
parses notes: “Project my_porg { Note: 'porgs are cool!' }” => DBML::ProjectDef.new('my_porg', ['porgs are cool!'], {})PROJECT_DEFINITION
parses settings: “Project my_cool {ndatabase_type: 'PostgreSQL'n}” => DBML::ProjectDef.new('my_cool', [], {database_type: 'PostgreSQL'})- QUOTED_IDENTIFIER
- REF_SETTING
Each setting item can take in 2 forms: Key: Value or keyword, similar to that of Python function parameters. Settings are all defined within square brackets: [setting1: value1, setting2: value2, setting3, setting4]
SETTINGS
parses key value settings: '[default: 123]' => {default: 123}SETTINGS
parses keyword settings: '[not null]' => {:'not null' => nil}SETTINGS
parses many settings: “[some setting: 'value', primary key]” => {:'some setting' => 'value', :'primary key' => nil}SETTINGS
parses keyword values: “[delete: cascade]” => {delete: :cascade}SETTINGS
parses relationship form: '[ref: > users.id]' => {ref: [DBML::Relationship.new(nil, nil, [], '>', 'users', ['id'], {})]}SETTINGS
parses multiple relationships: '[ref: > a.b, ref: < c.d]' => {ref: [DBML::Relationship.new(nil, nil, [], '>', 'a', ['b'], {}), DBML::Relationship.new(nil, nil, [], '<', 'c', ['d'], {})]}- RELATIONSHIP
- RELATIONSHIP_BODY
- RELATIONSHIP_PART
- RELATIONSHIP_TYPE
- RESERVED_PUNCTUATION
- SETTING
- SETTINGS
- SINGLE_LING_STRING
- STRING
STRING
parses blank strings: “''” => “”STRING
parses double quotes: '“”' => “”- TABLE
- TABLE_GROUP
TableGroup
¶ ↑
TableGroup allows users to group the related or associated tables together.
TableGroup tablegroup_name { // tablegroup is case-insensitive. table1 table2 table3 }
TABLE_GROUP
parses names: 'TableGroup group1 { }' => DBML::TableGroup.new('group1', [])TABLE_GROUP
parses tables: “TableGroup group2 {ntable1ntable2n}” => DBML::TableGroup.new('group2', ['table1', 'table2'])- TABLE_NAME
Table Definition
Table table_name { column_name column_type [column_settings] }
-
title of database table is listed as table_name
-
list is wrapped in curly brackets {}, for indexes, constraints and table definitions.
-
string value is be wrapped in a single quote as 'string'
TABLE_NAME
parses identifiers: 'table_name' => ['table_name', nil]TABLE_NAME
parses aliases: 'table_name as thingy' => ['table_name', 'thingy']TABLE
parses empty tables: 'Table empty {}' => DBML::Table.new('empty', nil, [], [], [])TABLE
parses notes: “Table with_notes {nNote: 'this is a note'n}” => DBML::Table.new('with_notes', nil, ['this is a note'], [], [])-
Public Class Methods
# File lib/dbml.rb, line 34 def self.block type, name_parser, content_parser, &block seq_(type.r >> name_parser, '{'.r >> space_surrounded(content_parser).star.map {|a| a.flatten(1) } << '}'.r, &block) end
# File lib/dbml.rb, line 26 def self.comma_separated p p.join(/, */.r.map {|_| nil}).star.map {|v| (v.first || []).reject(&:nil?) } end
# File lib/dbml.rb, line 18 def self.long_or_short p (':'.r >> p) | ('{'.r >> p << '}'.r) end
# File lib/dbml.rb, line 334 def self.parse str PROJECT.eof.parse! str.gsub(/\/{2}.*$/, '') end
# File lib/dbml.rb, line 30 def self.space_surrounded p /\s*/.r >> p << /\s*/.r end
# File lib/dbml.rb, line 22 def self.unwrap p, *_ if p.empty? then nil else p.first end end