Class: Tina4::Drivers::PostgresDriver

Inherits:
Object
  • Object
show all
Defined in:
lib/tina4/drivers/postgres_driver.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#connectionObject (readonly)

Returns the value of attribute connection.



6
7
8
# File 'lib/tina4/drivers/postgres_driver.rb', line 6

def connection
  @connection
end

Instance Method Details

#apply_limit(sql, limit, offset = 0) ⇒ Object



99
100
101
# File 'lib/tina4/drivers/postgres_driver.rb', line 99

def apply_limit(sql, limit, offset = 0)
  "#{sql} LIMIT #{limit} OFFSET #{offset}"
end

#begin_transactionObject



103
104
105
# File 'lib/tina4/drivers/postgres_driver.rb', line 103

def begin_transaction
  @connection.exec("BEGIN")
end

#closeObject



27
28
29
# File 'lib/tina4/drivers/postgres_driver.rb', line 27

def close
  @connection&.close
end

#columns(table_name) ⇒ Object



121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/tina4/drivers/postgres_driver.rb', line 121

def columns(table_name)
  sql = "SELECT column_name, data_type, is_nullable, column_default FROM information_schema.columns WHERE table_name = $1"
  rows = execute_query(sql, [table_name])
  rows.map do |r|
    {
      name: r[:column_name],
      type: r[:data_type],
      nullable: r[:is_nullable] == "YES",
      default: r[:column_default],
      primary_key: false
    }
  end
end

#commitObject



107
108
109
# File 'lib/tina4/drivers/postgres_driver.rb', line 107

def commit
  @connection.exec("COMMIT")
end

#connect(connection_string, username: nil, password: nil) ⇒ Object



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/tina4/drivers/postgres_driver.rb', line 8

def connect(connection_string, username: nil, password: nil)
  begin
    require "pg"
  rescue LoadError
    raise LoadError,
          "The 'pg' gem is required for PostgreSQL connections. Install one of:\n" \
          "    bundle add pg     # if your project uses Bundler\n" \
          "    gem install pg    # bare driver"
  end
  url = connection_string
  if username || password
    uri = URI.parse(url)
    uri.user = username if username
    uri.password = password if password
    url = uri.to_s
  end
  @connection = PG.connect(url)
end

#execute(sql, params = []) ⇒ Object



41
42
43
44
45
46
47
48
# File 'lib/tina4/drivers/postgres_driver.rb', line 41

def execute(sql, params = [])
  converted_sql = convert_placeholders(sql)
  if params.empty?
    @connection.exec(converted_sql)
  else
    @connection.exec_params(converted_sql, params)
  end
end

#execute_query(sql, params = []) ⇒ Object



31
32
33
34
35
36
37
38
39
# File 'lib/tina4/drivers/postgres_driver.rb', line 31

def execute_query(sql, params = [])
  converted_sql = convert_placeholders(sql)
  result = if params.empty?
             @connection.exec(converted_sql)
           else
             @connection.exec_params(converted_sql, params)
           end
  result.map { |row| decode_blobs(symbolize_keys(row)) }
end

#last_insert_idObject



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/tina4/drivers/postgres_driver.rb', line 50

def last_insert_id
  # Issue #38: ``SELECT lastval()`` raises on tables with no sequence
  # (UUID, ULID, hash PKs etc.). The exception itself isn't fatal,
  # but the pg gem marks the whole transaction as aborted, so every
  # subsequent statement on this connection fails with
  # ``PG::InFailedSqlTransaction`` — far away from the real cause.
  #
  # Fix: wrap the probe in a SAVEPOINT. If ``lastval()`` raises, we
  # ROLLBACK TO SAVEPOINT and the outer transaction stays usable;
  # ``last_insert_id`` just returns ``nil`` (same as before for
  # tables without a sequence). On success we RELEASE SAVEPOINT.
  begin
    @connection.exec("SAVEPOINT _t4_lastval_probe")
  rescue PG::Error
    # No active transaction (autocommit/idle) — fall back to a plain
    # probe; psycopg2-style transaction abort can't happen here.
    begin
      result = @connection.exec("SELECT lastval()")
      return result.first["lastval"].to_i
    rescue PG::Error
      return nil
    end
  end

  begin
    result = @connection.exec("SELECT lastval()")
    @connection.exec("RELEASE SAVEPOINT _t4_lastval_probe")
    result.first["lastval"].to_i
  rescue PG::Error
    begin
      @connection.exec("ROLLBACK TO SAVEPOINT _t4_lastval_probe")
      @connection.exec("RELEASE SAVEPOINT _t4_lastval_probe")
    rescue PG::Error
      # If even the rollback fails, there's nothing we can do — the
      # connection is in a state we can't recover. Surface nil so
      # callers don't get a half-set last_id.
    end
    nil
  end
end

#placeholderObject



91
92
93
# File 'lib/tina4/drivers/postgres_driver.rb', line 91

def placeholder
  "?"
end

#placeholders(count) ⇒ Object



95
96
97
# File 'lib/tina4/drivers/postgres_driver.rb', line 95

def placeholders(count)
  (1..count).map { |i| "$#{i}" }.join(", ")
end

#rollbackObject



111
112
113
# File 'lib/tina4/drivers/postgres_driver.rb', line 111

def rollback
  @connection.exec("ROLLBACK")
end

#tablesObject



115
116
117
118
119
# File 'lib/tina4/drivers/postgres_driver.rb', line 115

def tables
  sql = "SELECT tablename FROM pg_tables WHERE schemaname = 'public'"
  rows = execute_query(sql)
  rows.map { |r| r[:tablename] }
end