Class: Ferret::Field

Inherits:
Object
  • Object
show all
Defined in:
lib/sql-ferret.rb

Constant Summary collapse

FF_PRIMARY_KEY =
[Ferret::Field]

flags

0x01
FF_EXPL_UNIQUE =
0x02
FF_OPTIONAL =
0x04
FF_UNCONSTRAINED =
0x08
FF_REFERENCE =
0x10
FF_GHOST =
0x20

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(table, name, spec, &thunk) ⇒ Field

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.



1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
# 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

Instance Attribute Details

#defaultObject (readonly)

Returns the value of attribute default.



1438
1439
1440
# File 'lib/sql-ferret.rb', line 1438

def default
  @default
end

#hauntObject (readonly)

Returns the value of attribute haunt.



1436
1437
1438
# File 'lib/sql-ferret.rb', line 1436

def haunt
  @haunt
end

#interpretationObject (readonly)

Returns the value of attribute interpretation.



1406
1407
1408
# File 'lib/sql-ferret.rb', line 1406

def interpretation
  @interpretation
end

#nameObject (readonly)

Returns the value of attribute name.



1402
1403
1404
# File 'lib/sql-ferret.rb', line 1402

def name
  @name
end

#refObject (readonly)

Returns the value of attribute ref.



1440
1441
1442
# File 'lib/sql-ferret.rb', line 1440

def ref
  @ref
end

#tableObject (readonly)

Returns the value of attribute table.



1400
1401
1402
# File 'lib/sql-ferret.rb', line 1400

def table
  @table
end

#typeObject (readonly)

Returns the value of attribute type.



1404
1405
1406
# File 'lib/sql-ferret.rb', line 1404

def type
  @type
end

Instance Method Details

#column?Boolean

Returns:

  • (Boolean)


1432
1433
1434
# File 'lib/sql-ferret.rb', line 1432

def column?
  return (@flags & FF_GHOST) == 0
end

#ghost?Boolean

Returns:

  • (Boolean)


1428
1429
1430
# File 'lib/sql-ferret.rb', line 1428

def ghost?
  return (@flags & FF_GHOST) != 0
end

#inspectObject



1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
# 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?Boolean

Returns:

  • (Boolean)


1412
1413
1414
# File 'lib/sql-ferret.rb', line 1412

def optional?
  return (@flags & FF_OPTIONAL) != 0
end

#primary_key?Boolean

Returns:

  • (Boolean)


1416
1417
1418
# File 'lib/sql-ferret.rb', line 1416

def primary_key?
  return (@flags & FF_PRIMARY_KEY) != 0
end

#reference?Boolean

Returns:

  • (Boolean)


1424
1425
1426
# File 'lib/sql-ferret.rb', line 1424

def reference?
  return (@flags & FF_REFERENCE) != 0
end

#sql_to_declareObject



1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
# 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?Boolean

Returns:

  • (Boolean)


1420
1421
1422
# File 'lib/sql-ferret.rb', line 1420

def unconstrained?
  return (@flags & FF_UNCONSTRAINED) != 0
end

#unique?Boolean

Returns:

  • (Boolean)


1408
1409
1410
# File 'lib/sql-ferret.rb', line 1408

def unique?
  return (@flags & (FF_PRIMARY_KEY | FF_EXPL_UNIQUE)) != 0
end