class Ferret::Field

Constants

FF_EXPL_UNIQUE
FF_GHOST
FF_OPTIONAL
FF_PRIMARY_KEY
[Ferret::Field]

flags

FF_REFERENCE
FF_UNCONSTRAINED

Attributes

default[R]
haunt[R]
interpretation[R]
name[R]
ref[R]
table[R]
type[R]

Public Class Methods

new(table, name, spec) { |proc do |schema| raise 'assertion failed' if ref ref_table = schema ugh 'unknown-table', table: ref_table_name \ unless ref_table ugh? referring_field: name, referring_field_table: name do if ref_field_name then ref = ref_table or ugh 'unknown-field', field: ref_field_name, table: name, significance: 'referred' else ref = primary_key or ugh 'no-primary-key', table: name, significance: 'referred' end ugh 'not-a-column', field: name, table: table.name, significance: 'referred' \| ... } click to toggle source

Note that the parser does not look up the referred and haunted columns, for at the parsing time, not all the columns are yet available so trying to look up forward references would spuriously fail. Instead, it creates ‘relocation thunks’ and [[yield]]:s them to the caller, who must arrange to have them called (in the same order as they were [[yield]]:ed) after the whole schema has been loaded and which will perform these lookups and fill in the corresponding slots in the structure.

Calls superclass method
# File lib/sql-ferret.rb, line 1451
def initialize table, name, spec, &thunk
  raise 'type mismatch' unless table.is_a? Ferret::Table
  raise 'type mismatch' unless name.is_a? String
  raise 'type mismatch' unless spec.is_a? String
  super()
  @table = table
  @name = name
  unless spec.strip =~ %r{\A
      (
      | (?<primary_key> \b primary \s+ key \s* ,)
      | (?<unique> \b unique \b)
      | (?<optional> \b optional \b)
      | \b ghost \b \s* (?<haunt> \b \w+ \b)
      )\s*
      ( (?<type> \b \w+ \b)
      | (?<unconstrained> \b unconstrained \b \s*)?
        \b ref \b \s* (?<ref_table> \w+)
          ( \s* \( \s* (?<ref_field> \w+) \s* \) )?
      )
      ( \s* = \s* (?<default> [^\s].*) )?
      \Z}x then
    ugh 'invalid-field-specification',
        input: spec
  end

  unless $~['haunt'] then
    # Do we know the type?
    if $~['type'] and !%w{
        integer real varchar text blob iso8601
        unix_time subsecond_unix_time
        json pretty_json yaml
        ruby_marshal packed_hex}.include? $~['type'] then
      ugh 'unknown-type', type: $~['type']
    end
  else
    # The regex above is a bit too permissive.
    if $~['type'] or $~['unconstrained'] or $~['default'] then
      ugh 'invalid-field-specification',
          input: spec
    end
  end

  if $~['primary_key'] and
      ($~['ref_table'] or $~['default']) then
    ugh 'invalid-field-specification',
        input: spec
  end

  @flags = 0
  @flags |= FF_PRIMARY_KEY if $~['primary_key']
  @flags |= FF_EXPL_UNIQUE if $~['unique']
  @flags |= FF_OPTIONAL if $~['optional']

  # The current [[$~]] is unlikely to survive until the
  # relocation thunk gets called, so we'll have to copy
  # [[ref_table]] and [[ref_field]] out of it, into local
  # variables.
  if ref_table_name = $~['ref_table'] then
    @flags |= FF_REFERENCE
    ref_field_name = $~['ref_field']
    yield(proc do |schema|
      raise 'assertion failed' if @ref
      ref_table = schema[ref_table_name]
      ugh 'unknown-table', table: ref_table_name \
          unless ref_table
      ugh? referring_field: @name,
          referring_field_table: @table.name do
        if ref_field_name then
          @ref = ref_table[ref_field_name] or
              ugh 'unknown-field', field: ref_field_name,
                  table: ref_table.name,
                  significance: 'referred'
        else
          @ref = ref_table.primary_key or
              ugh 'no-primary-key', table: ref_table.name,
                  significance: 'referred'
        end
        ugh 'not-a-column', field: @ref.name,
                table: ref.table.name,
                significance: 'referred' \
            unless @ref.column?
      end
      @type = @ref.type
    end)
  else
    @type = $~['type']
  end

  if haunt = $~['haunt'] then
    @flags |= FF_GHOST
    yield(proc do |schema|
      ugh? significance: 'relied-on-by-ghost-field',
          ghost_field: @name do
        @haunt = @table[haunt]
        unless @haunt then
          ugh 'unknown-field', field: haunt
        end
        unless @haunt.column? then
          ugh 'not-a-column', field: @haunt.name
        end
        @type ||= @haunt.type
        unless @haunt.type == @type then
          ugh 'ghost-field-type-mismatch',
              field: @name,
              table: @table.name,
              type: @type.downcase,
              haunted_column: @haunt.name,
              haunted_column_type: @haunt.type.downcase
        end
      end
    end)
  end

  @flags |= FF_UNCONSTRAINED if $~['unconstrained']
  @default = $~['default']

  if @type then
      # [[@type]] can be [[nil]] if it's a reference field.
      # Then, the type and interpretation will be later copied
      # from the referred column.
    case @type.downcase
      when 'iso8601', 'json' then
        @interpretation = @type.downcase.to_sym
        @type = 'varchar'
      when 'yaml', 'pretty_json' then
        @interpretation = @type.downcase.to_sym
        @type = 'text'
      when 'ruby_marshal' then
        @interpretation = @type.downcase.to_sym
        @type = 'blob'
      when 'unix_time' then
        @interpretation = @type.downcase.to_sym
        @type = 'integer'
      when 'subsecond_unix_time' then
        @interpretation = @type.downcase.to_sym
        @type = 'real'
      else
        @interpretation = nil
    end
  end

  return
end

Public Instance Methods

column?() click to toggle source
# File lib/sql-ferret.rb, line 1432
def column?
  return (@flags & FF_GHOST) == 0
end
ghost?() click to toggle source
# File lib/sql-ferret.rb, line 1428
def ghost?
  return (@flags & FF_GHOST) != 0
end
inspect() click to toggle source
# File lib/sql-ferret.rb, line 1378
def inspect
  result = "#<Ferret::Field #{@table.name}.#{name}: "
  if primary_key? then
    result << 'primary key '
  else
    result << 'optional ' if optional?
    result << 'unique ' if unique?
  end
  if reference? then
    result << 'unconstrained ' if unconstrained?
    result << "ghost #{@haunt.name} " if ghost?
    result << 'ref %s(%s)' % [ref.table.name, ref.name]
  else
    result << (interpretation || type).to_s
  end
  # Note that [[default]] is an unsanitised, unprocessed
  # string extracted from the schema.  In pathological cases,
  # it can potentially contain the [[>]] character.
  result << " = #{default}" if default
  result << '>'
end
optional?() click to toggle source
# File lib/sql-ferret.rb, line 1412
def optional?
  return (@flags & FF_OPTIONAL) != 0
end
primary_key?() click to toggle source
# File lib/sql-ferret.rb, line 1416
def primary_key?
  return (@flags & FF_PRIMARY_KEY) != 0
end
reference?() click to toggle source
# File lib/sql-ferret.rb, line 1424
def reference?
  return (@flags & FF_REFERENCE) != 0
end
sql_to_declare() click to toggle source
# File lib/sql-ferret.rb, line 1603
def sql_to_declare
  sql = "#@name #@type"
  if primary_key? then
    sql << " primary key"
  else
    sql << " unique" if unique?
    sql << " not null" unless optional?
    sql << " default #@default" if default
  end
  if reference? and !unconstrained? then
    sql << "\n    references %s(%s)" %
        [@ref.table.name, @ref.name]
  end
  return sql
end
unconstrained?() click to toggle source
# File lib/sql-ferret.rb, line 1420
def unconstrained?
  return (@flags & FF_UNCONSTRAINED) != 0
end
unique?() click to toggle source
# File lib/sql-ferret.rb, line 1408
def unique?
  return (@flags & (FF_PRIMARY_KEY | FF_EXPL_UNIQUE)) != 0
end