Module: Mao

Defined in:
lib/mao.rb,
lib/mao/version.rb

Overview

The top-level module to access Mao.

Defined Under Namespace

Modules: Filter Classes: Query

Constant Summary collapse

Rollback =

When raised in a transaction, causes a rollback without the exception bubbling.

Class.new(Exception)
VERSION =

The version of the last release of Mao made. Increment when releasing a new gem.

"0.0.7"

Class Method Summary collapse

Class Method Details

.connect!(options) ⇒ Object

Connect to the database. options is currently the straight Postgres gem options.



10
11
12
13
14
15
# File 'lib/mao.rb', line 10

def self.connect!(options)
  unless @conn
    @conn = PG.connect(options)
    @conn.internal_encoding = Encoding::UTF_8
  end
end

.convert_type(value, type) ⇒ Object

Converts value to a native Ruby value, based on the PostgreSQL type type.



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/mao.rb', line 158

def self.convert_type(value, type)
  return nil if value.nil?

  case type
  when "integer", "smallint", "bigint", "serial", "bigserial"
    value.to_i
  when /^character varying/, "text"
    value
  when "timestamp without time zone"
    # We assume it's in UTC.  (Dangerous?)
    value =~ /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2}(?:\.\d+)?)$/
    Time.new($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_f, 0)
  when "boolean"
    value == "t"
  when "bytea"
    PG::Connection.unescape_bytea(value)
  when "numeric"
    BigDecimal.new(value)
  else
    STDERR.puts "#{self.name}: unknown type: #{type}"
    value
  end
end

.disconnect!Object

Disconnect from the database.



18
19
20
21
# File 'lib/mao.rb', line 18

def self.disconnect!
  @conn.close
  @conn = nil
end

.escape_literal(value) ⇒ Object

Escape value as appropriate for a literal in an SQL statement.



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/mao.rb', line 40

def self.escape_literal(value)
  case value
  when String
    if @conn.respond_to?(:escape_literal)
      @conn.escape_literal(value)
    else
      "'#{@conn.escape_string(value)}'"
    end
  when NilClass
    "null"
  when TrueClass
    "true"
  when FalseClass
    "false"
  when Numeric
    value.to_s
  when Array
    if value == []
      # NULL IN NULL is NULL in SQL, so this is "safe".  Empty lists () are
      # apparently part of the standard, but not widely supported (!).
      "(null)"
    else
      "(#{value.map {|v| escape_literal(v)}.join(", ")})"
    end
  when Mao::Query::Raw
    value.text
  when Time
    escape_literal(value.utc.strftime("%Y-%m-%d %H:%M:%S.%6N"))
  else
    raise ArgumentError, "don't know how to escape #{value.class}"
  end
end

.normalize_join_result(result, from_query, to_query) ⇒ Object

Normalizes the Hash result (of Strings to Strings), with the joining tables of from_query and to_query. Assumes the naming convention for result keys of Mao::Query#join (see Mao::Query#sql) has been followed.



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/mao.rb', line 127

def self.normalize_join_result(result, from_query, to_query)
  results = {}
  n = 0

  from_table = from_query.table
  from_types = from_query.col_types
  from_types.keys.sort.each do |k|
    n += 1
    key = "c#{n}"
    if result.include?(key)
      results[from_table] ||= {}
      results[from_table][k] = convert_type(result[key], from_types[k])
    end
  end

  to_table = to_query.table
  to_types = to_query.col_types
  to_types.keys.sort.each do |k|
    n += 1
    key = "c#{n}"
    if result.include?(key)
      results[to_table] ||= {}
      results[to_table][k] = convert_type(result[key], to_types[k])
    end
  end

  results
end

.normalize_result(result, col_types) ⇒ Object

Normalizes the Hash result (of Strings to Strings), with col_types specifying Symbol column names to String PostgreSQL types.



117
118
119
120
121
122
# File 'lib/mao.rb', line 117

def self.normalize_result(result, col_types)
  Hash[result.map {|k,v|
    k = k.to_sym
    [k, convert_type(v, col_types[k])]
  }]
end

.query(table) ⇒ Object

Returns a new Mao::Query object for table.



74
75
76
77
# File 'lib/mao.rb', line 74

def self.query(table)
  @queries ||= {}
  @queries[table] ||= Query.new(table).freeze
end

.quote_ident(name) ⇒ Object

Quote name as appropriate for a table or column name in an SQL statement.



30
31
32
33
34
35
36
37
# File 'lib/mao.rb', line 30

def self.quote_ident(name)
  case name
  when Symbol
    @conn.quote_ident(name.to_s)
  else
    @conn.quote_ident(name)
  end
end

.sql(sql, *args, &block) ⇒ Object

Execute the raw SQL sql with positional args. The returned object varies depending on the database vendor.



25
26
27
# File 'lib/mao.rb', line 25

def self.sql(sql, *args, &block)
  @conn.exec(sql, *args, &block)
end

.transaction(&block) ⇒ Object

Executes block in a transaction.

If block executes without an exception, the transaction is committed.

If a Mao::Rollback is raised, the transaction is rolled back, and #transaction returns false.

If any other Exception is raised, the transaction is rolled back, and the exception is re-raised.

Otherwise, the transaction is committed, and the result of block is returned.



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/mao.rb', line 95

def self.transaction(&block)
  return block.call if @in_transaction
  @in_transaction = true

  sql("BEGIN")
  begin
    r = block.call
  rescue Rollback
    sql("ROLLBACK")
    return false
  rescue Exception
    sql("ROLLBACK")
    raise
  ensure
    @in_transaction = false
  end
  sql("COMMIT")
  r
end