module DBML::Parser

Constants

ATOM
BOOLEAN

ATOM parses true: 'true' => true ATOM parses false: 'false' => false ATOM parses null: 'null' => nil ATOM parses numbers: '123.45678' => 123.45678 ATOM 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): CREATE INDEX on users (created_at, country) # Index with an expression: CREATE INDEX ON films ( first_name + last_name ) # (bonus) Composite index with expression: CREATE INDEX 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

block(type, name_parser, content_parser, &block) click to toggle source
# 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
comma_separated(p) click to toggle source
# File lib/dbml.rb, line 26
def self.comma_separated p
  p.join(/, */.r.map {|_| nil}).star.map {|v| (v.first || []).reject(&:nil?) }
end
long_or_short(p) click to toggle source
# File lib/dbml.rb, line 18
def self.long_or_short p
  (':'.r >> p) | ('{'.r >> p << '}'.r)
end
parse(str) click to toggle source
# File lib/dbml.rb, line 334
def self.parse str
  PROJECT.eof.parse! str.gsub(/\/{2}.*$/, '')
end
space_surrounded(p) click to toggle source
# File lib/dbml.rb, line 30
def self.space_surrounded p
  /\s*/.r >> p << /\s*/.r
end
unwrap(p, *_) click to toggle source
# File lib/dbml.rb, line 22
def self.unwrap p, *_
  if p.empty? then nil else p.first end
end