taupe 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rubocop.yml +10 -0
- data/Gemfile +6 -0
- data/LICENSE +21 -0
- data/README.md +176 -0
- data/Rakefile +28 -0
- data/lib/taupe/cache/memcached.rb +40 -0
- data/lib/taupe/cache/redis.rb +41 -0
- data/lib/taupe/cache.rb +95 -0
- data/lib/taupe/core.rb +74 -0
- data/lib/taupe/database/mysql.rb +70 -0
- data/lib/taupe/database/postgresql.rb +89 -0
- data/lib/taupe/database/sqlite.rb +73 -0
- data/lib/taupe/database.rb +138 -0
- data/lib/taupe/model/table.rb +192 -0
- data/lib/taupe/model/validate.rb +50 -0
- data/lib/taupe/model.rb +47 -0
- data/lib/taupe.rb +26 -0
- data/spec/database_spec.rb +59 -0
- data/spec/model_spec.rb +56 -0
- data/spec/taupe_spec.rb +14 -0
- data/spec/validator_spec.rb +43 -0
- data/taupe.gemspec +22 -0
- metadata +66 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f1b548f21b57a427e78515d3ea17ac0974f3a9d0
|
4
|
+
data.tar.gz: d25d1f8a2dd03b30b583e48f99a353fa09597528
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ef228a38b35977d1cada2add8000afeacf0d7756b42714d27d3b5806eb134193a0aa5f376aa56582440c8bd9451c7e52cc94c4b24b65593a01c5f764be248581
|
7
|
+
data.tar.gz: 560d21ddc307e63daa65469bd566b9c8062ec1d99aabf7ade937be744e70fa4bf199047aceef660d2364604a7843ee16e7ae814480639b1462c4b0c02b6bdc91
|
data/.rubocop.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 Pierre Lecocq
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,176 @@
|
|
1
|
+
# Taupe
|
2
|
+
|
3
|
+
## What is it?
|
4
|
+
|
5
|
+
**Taupe** is a simple and elegant database model manager.
|
6
|
+
|
7
|
+
It relies on several popular backends:
|
8
|
+
|
9
|
+
- *postgresql*, *mysql* or *sqlite* for database engines
|
10
|
+
- *memcached* or *redis* for cache engines
|
11
|
+
|
12
|
+
The main idea is to set a database connection, an optional cache connection and your models definitions.
|
13
|
+
That's it.
|
14
|
+
|
15
|
+
Then, you can query on the database connection or on your models directly. If a cache backend is defined, the data will be stored and retrieved faster than ever.
|
16
|
+
|
17
|
+
No fancy SQL DSL. Just pure SQL. Let's be braves and responsibles.
|
18
|
+
|
19
|
+
## Why developping it?
|
20
|
+
|
21
|
+
It was developped for two main pros:
|
22
|
+
|
23
|
+
- control
|
24
|
+
- power
|
25
|
+
|
26
|
+
I can hear you: "But for this, there are some very popular ORMs like *ActiveRecord* or *Sequel*, you #@$!&+ ! "
|
27
|
+
|
28
|
+
Yes.
|
29
|
+
But No.
|
30
|
+
Definitely.
|
31
|
+
|
32
|
+
Here are the main reasons:
|
33
|
+
|
34
|
+
- ORMs are too complex for a large percent of our everyday use projects
|
35
|
+
- ORMs do not give you the opportunity to write SQL and control your queries (no control, no power). Well, yes they do, but for this, they load so many unused classes that your application's performances are really affected.
|
36
|
+
- ORMs have limits on joins, sub-queries, CTEs (... etc) and this is frustrating when doing hard SQL work like statistics
|
37
|
+
|
38
|
+
## How to use it?
|
39
|
+
|
40
|
+
### Prerequisites
|
41
|
+
|
42
|
+
In order to install the Taupe library, simply install the corresponding gem with
|
43
|
+
|
44
|
+
sudo gem install taupe
|
45
|
+
|
46
|
+
After that, you must have a few gems install on your system, depending on which backends you want to use
|
47
|
+
|
48
|
+
# If you use PostgreSQL database
|
49
|
+
sudo gem install pg
|
50
|
+
|
51
|
+
# If you use MySQL database
|
52
|
+
sudo gem install mysql2
|
53
|
+
|
54
|
+
# If you use Sqlite database
|
55
|
+
sudo gem install sqlite3
|
56
|
+
|
57
|
+
# If you use Memcached
|
58
|
+
sudo gem install memcached
|
59
|
+
|
60
|
+
# If you use Redis
|
61
|
+
sudo gem install redis
|
62
|
+
|
63
|
+
### Define a SQL backend
|
64
|
+
|
65
|
+
#### Postgresql (highly recommended)
|
66
|
+
|
67
|
+
# Setup a postgresql database backend
|
68
|
+
Taupe::Database.setup do
|
69
|
+
type :postgresql
|
70
|
+
host :localhost
|
71
|
+
username :myuser
|
72
|
+
password :myfuckingstrongpassword
|
73
|
+
database :mydatabase
|
74
|
+
end
|
75
|
+
|
76
|
+
#### MySQL (not recommended)
|
77
|
+
|
78
|
+
# Setup a mysql database backend
|
79
|
+
Taupe::Database.setup do
|
80
|
+
type :mysql
|
81
|
+
host :localhost
|
82
|
+
username :myuser
|
83
|
+
password :myfuckingstrongpassword
|
84
|
+
database :mydatabase
|
85
|
+
end
|
86
|
+
|
87
|
+
#### SQLite
|
88
|
+
|
89
|
+
# Setup a sqlite database backend
|
90
|
+
Taupe::Database.setup do
|
91
|
+
type :sqlite
|
92
|
+
database File.expand_path('/tmp/mydatabase.db')
|
93
|
+
end
|
94
|
+
|
95
|
+
### Optionally define a cache backend (recommended)
|
96
|
+
|
97
|
+
#### Memcached
|
98
|
+
|
99
|
+
# Setup a memcached cache backend
|
100
|
+
Taupe::Cache.setup do
|
101
|
+
type :memcached
|
102
|
+
host :localhost
|
103
|
+
port :11211
|
104
|
+
end
|
105
|
+
|
106
|
+
#### Redis
|
107
|
+
|
108
|
+
# Setup a redis cache backend
|
109
|
+
Taupe::Cache.setup do
|
110
|
+
type :redis
|
111
|
+
host :localhost
|
112
|
+
port :6379
|
113
|
+
end
|
114
|
+
|
115
|
+
### Define some models
|
116
|
+
|
117
|
+
# Setup a model
|
118
|
+
Taupe::Model.setup do
|
119
|
+
table :article
|
120
|
+
column :article_id, { type: Integer, :primary_key => true }
|
121
|
+
column :title, { type: String, :null => false }
|
122
|
+
column :content, { type: String }
|
123
|
+
column :state, { type: Integer }
|
124
|
+
column :creation, { type: Date, :locked => true }
|
125
|
+
end
|
126
|
+
|
127
|
+
### And then? What can I do?
|
128
|
+
|
129
|
+
#### Load a new model object
|
130
|
+
|
131
|
+
article = Taupe::Model::Article.load
|
132
|
+
|
133
|
+
#### Load an existing model object
|
134
|
+
|
135
|
+
# A database ID
|
136
|
+
article_id = 3
|
137
|
+
|
138
|
+
# An optional cache key. Set it to nil if cache is not setted or needed
|
139
|
+
cache_key = "article_#{article_id}"
|
140
|
+
|
141
|
+
# Load model from database and create corresponding object
|
142
|
+
article = Taupe::Model::Article.load article_id, cache_key
|
143
|
+
|
144
|
+
#### Save a modified model object
|
145
|
+
|
146
|
+
# Modify the ruby object properties
|
147
|
+
article.title = 'This is my modified article title'
|
148
|
+
|
149
|
+
# Reflect changes in database/cache (insert or update)
|
150
|
+
article.save
|
151
|
+
|
152
|
+
#### Query, query, query. But by yourself.
|
153
|
+
|
154
|
+
# Execute a single query
|
155
|
+
Taupe::Database.exec "INSERT INTO article (title, state) VALUES ('Another article', 1)"
|
156
|
+
|
157
|
+
# Fetch results in an array
|
158
|
+
articles = Taupe::Database.fetch "SELECT * FROM article"
|
159
|
+
|
160
|
+
# Fetch a single result
|
161
|
+
article = Taupe::Database.fetch "SELECT * FROM article WHERE article_id = 3", true
|
162
|
+
|
163
|
+
For more clarity, you can also exec or fetch from any model class. It is exactly the same.
|
164
|
+
|
165
|
+
# Execute an insert query
|
166
|
+
Taupe::Model::Article.exec "INSERT INTO article (title, state) VALUES ('Another article', 1)"
|
167
|
+
|
168
|
+
# Fetch
|
169
|
+
articles = Taupe::Model::Article.fetch "SELECT * FROM article"
|
170
|
+
|
171
|
+
# Fetch single
|
172
|
+
article = Taupe::Model::Article.fetch "SELECT * FROM article WHERE article_id = 3", true
|
173
|
+
|
174
|
+
## License
|
175
|
+
|
176
|
+
Please see the [LICENSE](https://github.com/pierre-lecocq/taupe/blob/master/LICENSE) file
|
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
task :default => :help
|
2
|
+
|
3
|
+
# rake help
|
4
|
+
task :help do
|
5
|
+
puts 'Taupe - Available tasks:'
|
6
|
+
puts ' * test: run rspec tests'
|
7
|
+
puts ' * code: run rubocop to verify code quality'
|
8
|
+
puts ' * doc: run yard to generate documentation'
|
9
|
+
end
|
10
|
+
|
11
|
+
# rake test
|
12
|
+
require 'rspec/core/rake_task'
|
13
|
+
RSpec::Core::RakeTask.new(:test)
|
14
|
+
|
15
|
+
# rake code
|
16
|
+
require 'rubocop/rake_task'
|
17
|
+
desc 'Run RuboCop on the lib directory'
|
18
|
+
RuboCop::RakeTask.new(:code) do |task|
|
19
|
+
task.patterns = ['lib/**/*.rb']
|
20
|
+
task.fail_on_error = false
|
21
|
+
end
|
22
|
+
|
23
|
+
# rake doc
|
24
|
+
require 'yard'
|
25
|
+
YARD::Rake::YardocTask.new do |t|
|
26
|
+
t.files = ['lib/**/*.rb']
|
27
|
+
end
|
28
|
+
task :doc => :yard
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# File: memcached.rb
|
2
|
+
# Time-stamp: <2014-09-11 16:30:12 pierre>
|
3
|
+
# Copyright (C) 2014 Pierre Lecocq
|
4
|
+
# Description: Taupe library memcached driver class
|
5
|
+
|
6
|
+
module Taupe
|
7
|
+
class Cache
|
8
|
+
# Memcached cache driver
|
9
|
+
class MemcachedDriver
|
10
|
+
# Accessors
|
11
|
+
attr_accessor :connection
|
12
|
+
|
13
|
+
# Constructor
|
14
|
+
# @param dsn [Hash] The data source name
|
15
|
+
def initialize(dsn)
|
16
|
+
@connection = Memcached.new dsn
|
17
|
+
end
|
18
|
+
|
19
|
+
# Get a cache entry
|
20
|
+
# @param key [String] The key to retrieve
|
21
|
+
# @return [Object]
|
22
|
+
def get(key)
|
23
|
+
@connection.get key.to_s
|
24
|
+
end
|
25
|
+
|
26
|
+
# Set a cache entry
|
27
|
+
# @param key [String] The key to set
|
28
|
+
# @param value [Object] The value
|
29
|
+
def set(key, value)
|
30
|
+
@connection.set key.to_s, value
|
31
|
+
end
|
32
|
+
|
33
|
+
# Delete a key
|
34
|
+
# @param key [String] The key to delete
|
35
|
+
def delete(key)
|
36
|
+
@connection.delete key.to_s
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# File: redis.rb
|
2
|
+
# Time-stamp: <2014-09-11 16:28:45 pierre>
|
3
|
+
# Copyright (C) 2014 Pierre Lecocq
|
4
|
+
# Description: Taupe library redis driver class
|
5
|
+
|
6
|
+
module Taupe
|
7
|
+
class Cache
|
8
|
+
# Redis cache driver
|
9
|
+
class RedisDriver
|
10
|
+
# Accessors
|
11
|
+
attr_accessor :connection
|
12
|
+
|
13
|
+
# Constructor
|
14
|
+
# @param dsn [Hash] The data source name
|
15
|
+
def initialize(dsn)
|
16
|
+
@connection = Redis.new dsn
|
17
|
+
end
|
18
|
+
|
19
|
+
# Get a cache entry
|
20
|
+
# @param key [String] The key to retrieve
|
21
|
+
# @return [Object]
|
22
|
+
def get(key)
|
23
|
+
@connection.hgetall(key).symbolize_keys
|
24
|
+
end
|
25
|
+
|
26
|
+
# Set a cache entry
|
27
|
+
# @param key [String] The key to set
|
28
|
+
# @param value [Object] The value
|
29
|
+
def set(key, value)
|
30
|
+
fail 'Only HASH values are supported' unless value.is_a?(Hash)
|
31
|
+
@connection.mapped_hmset key, value
|
32
|
+
end
|
33
|
+
|
34
|
+
# Delete a key
|
35
|
+
# @param key [String] The key to delete
|
36
|
+
def delete(key)
|
37
|
+
@connection.del key
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/taupe/cache.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# File: cache.rb
|
3
|
+
# Time-stamp: <2014-08-22 16:59:32 pierre>
|
4
|
+
# Copyright (C) 2014 Pierre Lecocq
|
5
|
+
# Description: Taupe library cache class
|
6
|
+
|
7
|
+
require 'taupe/cache/memcached'
|
8
|
+
require 'taupe/cache/redis'
|
9
|
+
|
10
|
+
module Taupe
|
11
|
+
# Cache class
|
12
|
+
# Manage cache connection and serve as query proxy
|
13
|
+
class Cache
|
14
|
+
# Includes
|
15
|
+
include Accessorized
|
16
|
+
|
17
|
+
# Custom accessors
|
18
|
+
# Accessible via _name and _name=
|
19
|
+
single_accessor :type, :host, :port
|
20
|
+
|
21
|
+
# Accessors
|
22
|
+
attr_accessor :instance, :driver
|
23
|
+
|
24
|
+
# Constructor
|
25
|
+
# @param block [Proc] A given block
|
26
|
+
def initialize(&block)
|
27
|
+
instance_eval(&block)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Setup the Cache instance
|
31
|
+
# @param block [Proc] A given block
|
32
|
+
def self.setup(&block)
|
33
|
+
@instance = new(&block)
|
34
|
+
setup_defaults
|
35
|
+
driver_factory
|
36
|
+
end
|
37
|
+
|
38
|
+
# Setup default values
|
39
|
+
def self.setup_defaults
|
40
|
+
case @instance._type
|
41
|
+
when :memcached
|
42
|
+
Taupe.require_gem 'memcached', 'Memcached cache engine'
|
43
|
+
@instance._host ||= :localhost
|
44
|
+
@instance._port ||= 11_211
|
45
|
+
when :redis
|
46
|
+
Taupe.require_gem 'redis', 'Redis cache engine'
|
47
|
+
@instance._host ||= :localhost
|
48
|
+
@instance._port ||= 6379
|
49
|
+
else
|
50
|
+
fail 'Unknown cache type'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Get the data source name
|
55
|
+
# @return [Hash, String]
|
56
|
+
def self.dsn
|
57
|
+
case @instance._type
|
58
|
+
when :memcached
|
59
|
+
"#{@instance._host}:#{@instance._port}"
|
60
|
+
when :redis
|
61
|
+
{
|
62
|
+
host: @instance._host.to_s,
|
63
|
+
port: @instance._port.to_i
|
64
|
+
}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Setup the connection driver
|
69
|
+
def self.driver_factory
|
70
|
+
cname = "Taupe::Cache::#{@instance._type.capitalize}Driver"
|
71
|
+
klass = cname.split('::').reduce(Object) { |a, e| a.const_get e }
|
72
|
+
@instance.driver = klass.new dsn
|
73
|
+
end
|
74
|
+
|
75
|
+
# Get a cache entry
|
76
|
+
# @param key [String] The key to retrieve
|
77
|
+
# @return [Object]
|
78
|
+
def self.get(key)
|
79
|
+
@instance.driver.get key.to_s
|
80
|
+
end
|
81
|
+
|
82
|
+
# Set a cache entry
|
83
|
+
# @param key [String] The key to set
|
84
|
+
# @param value [Object] The value
|
85
|
+
def self.set(key, value)
|
86
|
+
@instance.driver.set key.to_s, value
|
87
|
+
end
|
88
|
+
|
89
|
+
# Delete a key
|
90
|
+
# @param key [String] The key to delete
|
91
|
+
def delete(key)
|
92
|
+
@instance.driver.delete key.to_s
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
data/lib/taupe/core.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# File: core.rb
|
2
|
+
# Time-stamp: <2014-08-22 16:34:51 pierre>
|
3
|
+
# Copyright (C) 2014 Pierre Lecocq
|
4
|
+
# Description: Taupe library core class
|
5
|
+
|
6
|
+
module Taupe
|
7
|
+
# Accessorized module
|
8
|
+
# Add the ability to set custom accessor to a class
|
9
|
+
module Accessorized
|
10
|
+
# Extend ClassMethods
|
11
|
+
# @param base [Object] The caller object
|
12
|
+
def self.included(base)
|
13
|
+
base.extend(ClassMethods)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Class methods
|
17
|
+
module ClassMethods
|
18
|
+
# Define single accessor. It can be set one time.
|
19
|
+
# @example single_accessor :name
|
20
|
+
# @note The getter is "_name" instead of "name"
|
21
|
+
# @param names [Array] Set of names
|
22
|
+
def single_accessor(*names)
|
23
|
+
names.each do |name|
|
24
|
+
define_method name do |data|
|
25
|
+
instance_variable_set "@#{name}".to_sym, data
|
26
|
+
end
|
27
|
+
define_method "_#{name}" do
|
28
|
+
instance_variable_get "@#{name}".to_sym
|
29
|
+
end
|
30
|
+
define_method "_#{name}=" do |data|
|
31
|
+
instance_variable_set "@#{name}".to_sym, data
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Define stacked accessor. It can be set several times.
|
37
|
+
# @example stacked_accessor :name, { type; String, null; false }
|
38
|
+
# @note The getter is "_name_stack" instead of "name"
|
39
|
+
# @param names [Array] Set of names, and a hash of values
|
40
|
+
def stacked_accessor(*names)
|
41
|
+
names.each do |name|
|
42
|
+
define_method name do |*data|
|
43
|
+
stack = instance_variable_get("@_#{name}_stack".to_sym) || {}
|
44
|
+
stack[data[0]] = data[1]
|
45
|
+
instance_variable_set "@_#{name}_stack".to_sym, stack
|
46
|
+
end
|
47
|
+
define_method "_#{name}_stack" do
|
48
|
+
instance_variable_get "@_#{name}_stack".to_sym
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Hash ruby core class monkey patching (yes, this is ugly)
|
57
|
+
class Hash
|
58
|
+
# Symbolize keys (credits to Avdi Grimm)
|
59
|
+
def symbolize_keys
|
60
|
+
each_with_object({}) do |(key, value), result|
|
61
|
+
new_key = case key
|
62
|
+
when String then key.to_sym
|
63
|
+
else key
|
64
|
+
end
|
65
|
+
new_val = case value
|
66
|
+
when Hash then symbolize_keys(value)
|
67
|
+
else value
|
68
|
+
end
|
69
|
+
result[new_key] = new_val
|
70
|
+
|
71
|
+
result
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# File: mysql.rb
|
2
|
+
# Time-stamp: <2014-09-11 16:29:52 pierre>
|
3
|
+
# Copyright (C) 2014 Pierre Lecocq
|
4
|
+
# Description: Taupe library mysql driver class
|
5
|
+
|
6
|
+
module Taupe
|
7
|
+
class Database
|
8
|
+
# Mysql database driver
|
9
|
+
class MysqlDriver
|
10
|
+
# Accessors
|
11
|
+
attr_accessor :connection
|
12
|
+
|
13
|
+
# Constructor
|
14
|
+
# @param dsn [Hash] The data source name
|
15
|
+
def initialize(dsn)
|
16
|
+
dsn[:host] = '127.0.0.1' if dsn[:host].to_s == 'localhost'
|
17
|
+
@connection = Mysql2::Client.new dsn
|
18
|
+
@connection.query_options.merge! symbolize_keys: true
|
19
|
+
end
|
20
|
+
|
21
|
+
# Execute a single query
|
22
|
+
# @param query [String] The query to execute
|
23
|
+
# @return [Object]
|
24
|
+
def exec(query)
|
25
|
+
@connection.query query
|
26
|
+
end
|
27
|
+
|
28
|
+
# Fetch objects from database
|
29
|
+
# @param query [String] The query to fetch
|
30
|
+
# @return [Array, Object]
|
31
|
+
def fetch(query)
|
32
|
+
exec(query).to_a
|
33
|
+
end
|
34
|
+
|
35
|
+
# Get last inserted id
|
36
|
+
# @return [Integer]
|
37
|
+
def last_id
|
38
|
+
@connection.last_id.to_i
|
39
|
+
end
|
40
|
+
|
41
|
+
# Guess schema of a table
|
42
|
+
# @param table [String] The table name
|
43
|
+
# @return [Hash]
|
44
|
+
def guess_schema(table)
|
45
|
+
results = {}
|
46
|
+
|
47
|
+
query = format('SHOW COLUMNS FROM %s', table)
|
48
|
+
|
49
|
+
fetch(query).each do |values|
|
50
|
+
type = Taupe::Validate.standardize_sql_type values[:Type]
|
51
|
+
|
52
|
+
results[values[:Field].to_sym] = {
|
53
|
+
type: type,
|
54
|
+
null: values[:Null] != 'NO',
|
55
|
+
primary_key: values[:Key] == 'PRI'
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
results
|
60
|
+
end
|
61
|
+
|
62
|
+
# Escape a string
|
63
|
+
# @param str [String]
|
64
|
+
# @return [String]
|
65
|
+
def escape(str)
|
66
|
+
@connection.escape str
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# File: postgresql.rb
|
2
|
+
# Time-stamp: <2014-09-11 16:29:13 pierre>
|
3
|
+
# Copyright (C) 2014 Pierre Lecocq
|
4
|
+
# Description: Taupe library postgresql driver class
|
5
|
+
|
6
|
+
module Taupe
|
7
|
+
class Database
|
8
|
+
# Postgresql database driver
|
9
|
+
class PostgresqlDriver
|
10
|
+
# Accessors
|
11
|
+
attr_accessor :connection, :last_id
|
12
|
+
|
13
|
+
# Constructor
|
14
|
+
# @param dsn [Hash] The data source name
|
15
|
+
def initialize(dsn)
|
16
|
+
@connection = PG::Connection.new dsn
|
17
|
+
end
|
18
|
+
|
19
|
+
# Execute a single query
|
20
|
+
# @param query [String] The query to execute
|
21
|
+
# @return [Object]
|
22
|
+
def exec(query)
|
23
|
+
result = @connection.exec query
|
24
|
+
|
25
|
+
@last_id = nil
|
26
|
+
@last_id = result[0].flatten[0] if query.upcase.include?('RETURNING')
|
27
|
+
|
28
|
+
result
|
29
|
+
end
|
30
|
+
|
31
|
+
# Fetch objects from database
|
32
|
+
# @param query [String] The query to fetch
|
33
|
+
# @return [Array, Object]
|
34
|
+
def fetch(query)
|
35
|
+
exec(query).to_a.map(&:symbolize_keys)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Get last inserted id
|
39
|
+
# @return [Integer]
|
40
|
+
def last_id
|
41
|
+
if @last_id.nil?
|
42
|
+
message = 'Last ID can not be retrieved.'
|
43
|
+
message << ' Maybe the last query did not include "RETURNING"'
|
44
|
+
|
45
|
+
warn message
|
46
|
+
end
|
47
|
+
|
48
|
+
@last_id.to_i
|
49
|
+
end
|
50
|
+
|
51
|
+
# Guess schema of a table
|
52
|
+
# @param table [String] The table name
|
53
|
+
# @return [Hash]
|
54
|
+
def guess_schema(table)
|
55
|
+
results = {}
|
56
|
+
|
57
|
+
query = 'SELECT'
|
58
|
+
query << ' column_name, data_type, character_maximum_length,'
|
59
|
+
query << ' column_default, is_nullable'
|
60
|
+
query << ' FROM INFORMATION_SCHEMA.COLUMNS'
|
61
|
+
query << format(' WHERE table_name = \'%s\'', table)
|
62
|
+
query << ' ORDER BY ordinal_position'
|
63
|
+
|
64
|
+
fetch(query).each do |values|
|
65
|
+
type = Taupe::Validate.standardize_sql_type values[:data_type]
|
66
|
+
pkey = false
|
67
|
+
unless values[:column_default].nil?
|
68
|
+
pkey = true unless values[:column_default].match('nextval').nil?
|
69
|
+
end
|
70
|
+
|
71
|
+
results[values[:column_name].to_sym] = {
|
72
|
+
type: type,
|
73
|
+
null: values[:is_nullable] != 'NO',
|
74
|
+
primary_key: pkey
|
75
|
+
}
|
76
|
+
end
|
77
|
+
|
78
|
+
results
|
79
|
+
end
|
80
|
+
|
81
|
+
# Escape a string
|
82
|
+
# @param str [String]
|
83
|
+
# @return [String]
|
84
|
+
def escape(str)
|
85
|
+
@connection.escape_string str
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|