intersys 0.1 → 0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/Makefile +9 -9
- data/lib/callable.rb +86 -0
- data/lib/database.c +21 -1
- data/lib/extconf.rb +33 -9
- data/lib/global.c +66 -0
- data/lib/intersys.c +24 -3
- data/lib/intersys.h +29 -2
- data/lib/intersys.rb +59 -350
- data/lib/intersys_adapter.rb +132 -0
- data/lib/object.rb +163 -0
- data/lib/query.c +143 -3
- data/lib/reflection.rb +113 -0
- data/lib/sql_include/iodbcunix.h +149 -0
- data/lib/sql_include/sql.h +1189 -0
- data/lib/sql_include/sqlext.h +2566 -0
- data/lib/sql_include/sqltypes.h +421 -0
- data/lib/sql_include/sqlucode.h +812 -0
- data/test/adapter.rb +19 -0
- data/test/global.rb +12 -0
- data/test/query.rb +5 -0
- data/test/reflection.rb +7 -6
- data/test/strings.rb +2 -1
- metadata +15 -3
- data/lib/test.rb +0 -30
@@ -0,0 +1,132 @@
|
|
1
|
+
# Author/Maintainer: Max Lapshin <max@maxidoors.ru>
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require_gem 'activerecord'
|
5
|
+
|
6
|
+
require 'active_record/connection_adapters/abstract_adapter'
|
7
|
+
|
8
|
+
module ActiveRecord
|
9
|
+
class Base
|
10
|
+
def self.intersys_connection(config)
|
11
|
+
config = config.symbolize_keys
|
12
|
+
require 'intersys'
|
13
|
+
intersys = Intersys::Database.new(config)
|
14
|
+
ConnectionAdapters::IntersysAdapter.new(intersys, logger, config)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module ConnectionAdapters
|
19
|
+
class IntersysAdapter < AbstractAdapter
|
20
|
+
attr_reader :config
|
21
|
+
def initialize(database, logger, config)
|
22
|
+
super(database, logger)
|
23
|
+
@config = config
|
24
|
+
end
|
25
|
+
|
26
|
+
def adapter_name #:nodoc:
|
27
|
+
'Intersys Caché'
|
28
|
+
end
|
29
|
+
|
30
|
+
def supports_migrations? #:nodoc:
|
31
|
+
false
|
32
|
+
end
|
33
|
+
|
34
|
+
def active?
|
35
|
+
warn "IntersysAdapter#active? not implemented"
|
36
|
+
return true
|
37
|
+
@connection.query "select 1"
|
38
|
+
rescue Intersys::IntersysException
|
39
|
+
false
|
40
|
+
end
|
41
|
+
|
42
|
+
def disconnect!
|
43
|
+
@connection.close! rescue nil
|
44
|
+
end
|
45
|
+
|
46
|
+
def reconnect!
|
47
|
+
disconnect!
|
48
|
+
connect
|
49
|
+
end
|
50
|
+
|
51
|
+
def begin_db_transaction
|
52
|
+
@connection.start
|
53
|
+
end
|
54
|
+
def commit_db_transaction
|
55
|
+
@connection.commit
|
56
|
+
end
|
57
|
+
def rollback_db_transaction
|
58
|
+
@connection.rollback
|
59
|
+
end
|
60
|
+
|
61
|
+
def add_limit_offset!(sql, options)
|
62
|
+
sql << " LIMIT #{limit}" if limit = options[:limit]
|
63
|
+
sql << " OFFSET #{offset}" if offset = options[:offset]
|
64
|
+
end
|
65
|
+
|
66
|
+
def select_all(sql, name = nil) #:nodoc:
|
67
|
+
select(sql, name)
|
68
|
+
end
|
69
|
+
|
70
|
+
def select_one(sql, name = nil) #:nodoc:
|
71
|
+
result = select(sql, name)
|
72
|
+
result.nil? ? nil : result.first
|
73
|
+
end
|
74
|
+
|
75
|
+
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
|
76
|
+
execute(sql, name = nil)
|
77
|
+
id_value || @connection.insert_id
|
78
|
+
end
|
79
|
+
|
80
|
+
def update(sql, name = nil) #:nodoc:
|
81
|
+
execute(sql, name)
|
82
|
+
@connection.affected_rows
|
83
|
+
end
|
84
|
+
|
85
|
+
alias_method :delete, :update #:nodoc:
|
86
|
+
|
87
|
+
|
88
|
+
|
89
|
+
def columns(table_name, name = nil)
|
90
|
+
@cdef = Intersys::Reflection::ClassDefinition.open("User.#{table_name.camelize}")
|
91
|
+
result = [IntersysColumn.new("id", "", "int")]
|
92
|
+
@cdef.properties.each do |prop|
|
93
|
+
result << IntersysColumn.new(prop.sql_field_name, prop.initial_expression, prop.Type.gsub("%","").underscore, prop.required)
|
94
|
+
end
|
95
|
+
result
|
96
|
+
end
|
97
|
+
|
98
|
+
def native_database_types
|
99
|
+
{
|
100
|
+
:primary_key => 'int generated by default as identity (start with 42) primary key',
|
101
|
+
:string => { :name => 'varchar', :limit => 255 },
|
102
|
+
:text => { :name => 'clob', :limit => 32768 },
|
103
|
+
:integer => { :name => 'int' },
|
104
|
+
:float => { :name => 'float' },
|
105
|
+
:datetime => { :name => 'timestamp' },
|
106
|
+
:timestamp => { :name => 'timestamp' },
|
107
|
+
:time => { :name => 'time' },
|
108
|
+
:date => { :name => 'date' },
|
109
|
+
:binary => { :name => 'blob', :limit => 32768 },
|
110
|
+
:boolean => { :name => 'decimal', :limit => 1 }
|
111
|
+
}
|
112
|
+
end
|
113
|
+
|
114
|
+
protected
|
115
|
+
# :nodoc
|
116
|
+
def connect
|
117
|
+
@connection = Intersys::Database.new(config)
|
118
|
+
end
|
119
|
+
|
120
|
+
def select(sql, name = nil)
|
121
|
+
@connection.query(sql)
|
122
|
+
end
|
123
|
+
|
124
|
+
def execute(sql, name = nil)
|
125
|
+
@connection.execute(sql)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
class IntersysColumn < Column
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
data/lib/object.rb
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
module Intersys
|
2
|
+
# Basic class for all classes, through which is performed access to Cache classes
|
3
|
+
# For each Cache class must be created ruby class, inherited from Intersys::Object
|
4
|
+
#
|
5
|
+
# By default prefix "User" is selected. If Cache class has another prefix, it must
|
6
|
+
# be provided explicitly via method "prefix":
|
7
|
+
# class List < Intersys::Object
|
8
|
+
# prefix "%Library"
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
# By default name of Cache class is taken the same as name of ruby class.
|
12
|
+
# Thus in this example this class List will be marshalled to Cache class
|
13
|
+
# %Library.List
|
14
|
+
#
|
15
|
+
class Object
|
16
|
+
|
17
|
+
class << self
|
18
|
+
protected
|
19
|
+
def class_names
|
20
|
+
common_get_or_set("@class_names", {})
|
21
|
+
end
|
22
|
+
|
23
|
+
def prefix=(name)
|
24
|
+
@prefix = name
|
25
|
+
@class_name = @prefix + "." + (@class_name ? @class_name.split(".").last : self.to_s)
|
26
|
+
register_name!
|
27
|
+
end
|
28
|
+
|
29
|
+
def class_name=(name)
|
30
|
+
if name.index(".")
|
31
|
+
self.prefix = name.split(".").first
|
32
|
+
@class_name = name
|
33
|
+
else
|
34
|
+
@class_name = self.prefix + "." + name
|
35
|
+
end
|
36
|
+
register_name!
|
37
|
+
end
|
38
|
+
|
39
|
+
# Register class name of current class in global list
|
40
|
+
def register_name!
|
41
|
+
if i = class_names.index(self)
|
42
|
+
class_names.delete(i)
|
43
|
+
end
|
44
|
+
class_names[class_name] = self
|
45
|
+
class_name
|
46
|
+
end
|
47
|
+
|
48
|
+
public
|
49
|
+
# Nice function, that generates description of Cache class, looking just as C++ one
|
50
|
+
# Maybe, later, it will be possible even to generate IDL, using this code
|
51
|
+
def intersys_description
|
52
|
+
"class #{class_name} { \n" + intersys_reflector.all_methods.map do |mtd|
|
53
|
+
begin
|
54
|
+
"\t"+intersys_method(mtd).description+";\n"
|
55
|
+
rescue
|
56
|
+
"\tundefined #{mtd}\n"
|
57
|
+
end
|
58
|
+
end.join("") + "};"
|
59
|
+
end
|
60
|
+
|
61
|
+
# Help to work with instance variables of Intersys::Object class
|
62
|
+
# required, because list of registered descendants of Intersys::Object,
|
63
|
+
# database connection etc. should be in one place
|
64
|
+
def common_get_or_set(name, default_value = nil)
|
65
|
+
unless var = Intersys::Object.instance_variable_get(name)
|
66
|
+
default = block_given? ? yield : default_value
|
67
|
+
var = Intersys::Object.instance_variable_set(name, default)
|
68
|
+
end
|
69
|
+
var
|
70
|
+
end
|
71
|
+
|
72
|
+
# Takes Cache class name and try to resolve it to Ruby class
|
73
|
+
def lookup(class_name)
|
74
|
+
class_names[class_name] || raise(UnMarshallError, "Couldn't find registered class with Cache name '#{class_name}'")
|
75
|
+
end
|
76
|
+
|
77
|
+
# Each Cache class has prefix before it's name: namespace.
|
78
|
+
# this method set's prefix for current class is provided,
|
79
|
+
# or just returns current prefix
|
80
|
+
def prefix(name = nil)
|
81
|
+
self.prefix = name if name
|
82
|
+
@prefix ||= "User"
|
83
|
+
end
|
84
|
+
|
85
|
+
# Returns Cache class name, if called without parameters, or sets one, if passed
|
86
|
+
def class_name(name = nil)
|
87
|
+
self.class_name = name if name
|
88
|
+
self.class_name = (prefix + "." + to_s) unless @class_name
|
89
|
+
@class_name
|
90
|
+
end
|
91
|
+
|
92
|
+
# Returns database, if called without parameters, or sets one, if passed
|
93
|
+
# Once established, it is not possible now to connect to another database
|
94
|
+
def database(db_options = {})
|
95
|
+
common_get_or_set("@database") do
|
96
|
+
Intersys::Database.new({:user => "_SYSTEM", :password => "SYS", :namespace => "User"}.merge(db_options))
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# This method takes block and executes it between
|
101
|
+
# START TRANSACTION and COMMIT TRANSACTION
|
102
|
+
#
|
103
|
+
# In case of exception ROLLBACK TRANSACTION is called
|
104
|
+
def transaction
|
105
|
+
return unless block_given?
|
106
|
+
database.start
|
107
|
+
begin
|
108
|
+
yield
|
109
|
+
database.commit
|
110
|
+
rescue StandardError => e
|
111
|
+
database.rollback
|
112
|
+
raise e
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# :nodoc
|
117
|
+
def inherited(klass)
|
118
|
+
class_names[klass.class_name] = klass
|
119
|
+
end
|
120
|
+
|
121
|
+
# Look into Cache documentation for what is concurrency. I don't know
|
122
|
+
def concurrency
|
123
|
+
1
|
124
|
+
end
|
125
|
+
|
126
|
+
# timeout for connection
|
127
|
+
def timeout
|
128
|
+
5
|
129
|
+
end
|
130
|
+
|
131
|
+
# Nice method, that deletes all instances of class.
|
132
|
+
# You can just Person.delete_extent, but Person.delete_all looks more like ActiveRecord
|
133
|
+
def delete_all
|
134
|
+
intersys_call("%DeleteExtent")
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
|
139
|
+
# You can ask for database from instance
|
140
|
+
def database
|
141
|
+
self.class.database
|
142
|
+
end
|
143
|
+
|
144
|
+
# You can ask from instance it's Cache class name
|
145
|
+
def class_name
|
146
|
+
self.class.class_name
|
147
|
+
end
|
148
|
+
|
149
|
+
# Returns id of current object.
|
150
|
+
# You can remove this method and You will get string ID, so leave it here
|
151
|
+
# However, if You ask reflector for id, it will give incorrect answer,
|
152
|
+
# because Cache allows id to be string
|
153
|
+
def id
|
154
|
+
intersys_call("%Id").to_i
|
155
|
+
end
|
156
|
+
|
157
|
+
# Destroys current object
|
158
|
+
def destroy
|
159
|
+
self.class.intersys_call("%DeleteId", id)
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|
163
|
+
end
|
data/lib/query.c
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
#include "intersys.h"
|
2
|
+
#ifdef HAVE_SQL_H
|
3
|
+
|
2
4
|
#ifdef __CYGWIN__
|
3
5
|
#include <windef.h>
|
4
6
|
#endif
|
@@ -31,8 +33,97 @@ VALUE intersys_query_initialize(VALUE self, VALUE database, VALUE sql_query) {
|
|
31
33
|
int sql_code;
|
32
34
|
Data_Get_Struct(self, struct rbQuery, query);
|
33
35
|
Data_Get_Struct(database, struct rbDatabase, base);
|
36
|
+
rb_iv_set(self, "@database", database);
|
37
|
+
query->limit = -1;
|
34
38
|
RUN(cbind_alloc_query(base->database, &query->query));
|
35
|
-
RUN(cbind_prepare_gen_query(query->query, WCHARSTR(sql_query), &sql_code));
|
39
|
+
RUN(cbind_prepare_gen_query(query->query, WCHARSTR(TOWCHAR(sql_query)), &sql_code));
|
40
|
+
return self;
|
41
|
+
}
|
42
|
+
|
43
|
+
static void query_bind_one_param(h_query query, int index, VALUE obj) {
|
44
|
+
int sql_type;
|
45
|
+
RUN(cbind_query_get_par_sql_type(query, index, &sql_type));
|
46
|
+
|
47
|
+
switch (sql_type) {
|
48
|
+
case SQL_CHAR:
|
49
|
+
case SQL_VARCHAR:
|
50
|
+
case SQL_LONGVARCHAR: {
|
51
|
+
VALUE str = rb_funcall(obj, rb_intern("to_s"), 0);
|
52
|
+
RUN(cbind_query_set_mb_str_par(query, index, STR(str), LEN(str)));
|
53
|
+
break;
|
54
|
+
}
|
55
|
+
case SQL_BINARY:
|
56
|
+
case SQL_LONGVARBINARY:
|
57
|
+
case SQL_VARBINARY: {
|
58
|
+
VALUE str = rb_funcall(obj, rb_intern("to_s"), 0);
|
59
|
+
RUN(cbind_query_set_bin_par(query, index, STR(str), LEN(str)));
|
60
|
+
break;
|
61
|
+
}
|
62
|
+
case SQL_TINYINT:
|
63
|
+
case SQL_SMALLINT:
|
64
|
+
case SQL_INTEGER:
|
65
|
+
case SQL_BIGINT:
|
66
|
+
case SQL_BIT:
|
67
|
+
{
|
68
|
+
VALUE num = rb_funcall(obj, rb_intern("to_i"), 0);
|
69
|
+
RUN(cbind_query_set_int_par(query, index, NUM2INT(num)));
|
70
|
+
break;
|
71
|
+
}
|
72
|
+
case SQL_FLOAT:
|
73
|
+
case SQL_DOUBLE:
|
74
|
+
case SQL_REAL:
|
75
|
+
case SQL_NUMERIC:
|
76
|
+
case SQL_DECIMAL:
|
77
|
+
{
|
78
|
+
VALUE f = rb_funcall(obj, rb_intern("to_f"), 0);
|
79
|
+
RUN(cbind_query_set_double_par(query, index, RFLOAT(f)->value));
|
80
|
+
break;
|
81
|
+
}
|
82
|
+
case SQL_TIME:
|
83
|
+
{
|
84
|
+
int hour = NUM2INT(CALL(obj, "hour"));
|
85
|
+
int minute = NUM2INT(CALL(obj, "min"));
|
86
|
+
int second = NUM2INT(CALL(obj, "sec"));
|
87
|
+
RUN(cbind_query_set_time_par(query, index, hour, minute, second));
|
88
|
+
break;
|
89
|
+
}
|
90
|
+
case SQL_DATE:
|
91
|
+
{
|
92
|
+
int year = NUM2INT(CALL(obj, "year"));
|
93
|
+
int month = NUM2INT(CALL(obj, "month"));
|
94
|
+
int day = NUM2INT(CALL(obj, "day"));
|
95
|
+
RUN(cbind_query_set_date_par(query, index, year, month, day));
|
96
|
+
break;
|
97
|
+
}
|
98
|
+
case SQL_TIMESTAMP:
|
99
|
+
{
|
100
|
+
int year = NUM2INT(CALL(obj, "year"));
|
101
|
+
int month = NUM2INT(CALL(obj, "month"));
|
102
|
+
int day = NUM2INT(CALL(obj, "day"));
|
103
|
+
int hour = NUM2INT(CALL(obj, "hour"));
|
104
|
+
int minute = NUM2INT(CALL(obj, "min"));
|
105
|
+
int second = NUM2INT(CALL(obj, "sec"));
|
106
|
+
int fraction = 0;
|
107
|
+
RUN(cbind_query_set_timestamp_par(query, index,
|
108
|
+
year, month, day, hour, minute, second, fraction));
|
109
|
+
break;
|
110
|
+
}
|
111
|
+
|
112
|
+
default:
|
113
|
+
rb_raise(cMarshallError, "unknown sql type %d for parameter N %d", sql_type, index);
|
114
|
+
}
|
115
|
+
}
|
116
|
+
|
117
|
+
|
118
|
+
VALUE intersys_query_bind_params(VALUE self, VALUE params) {
|
119
|
+
int i;
|
120
|
+
struct rbQuery* query;
|
121
|
+
Check_Type(params, T_ARRAY);
|
122
|
+
Data_Get_Struct(self, struct rbQuery, query);
|
123
|
+
|
124
|
+
for(i = 0; i < RARRAY(params)->len; i++) {
|
125
|
+
query_bind_one_param(query->query, i, RARRAY(params)->ptr[i]);
|
126
|
+
}
|
36
127
|
return self;
|
37
128
|
}
|
38
129
|
|
@@ -47,6 +138,7 @@ VALUE intersys_query_execute(VALUE self) {
|
|
47
138
|
return self;
|
48
139
|
}
|
49
140
|
|
141
|
+
|
50
142
|
VALUE intersys_query_get_data(VALUE self, VALUE index) {
|
51
143
|
struct rbQuery* query;
|
52
144
|
int type = 0;
|
@@ -129,7 +221,7 @@ VALUE intersys_query_column_name(VALUE self, VALUE i) {
|
|
129
221
|
|
130
222
|
VALUE intersys_query_fetch(VALUE self) {
|
131
223
|
struct rbQuery* query;
|
132
|
-
VALUE data;
|
224
|
+
VALUE data = Qnil;
|
133
225
|
Data_Get_Struct(self, struct rbQuery, query);
|
134
226
|
int num_cols = 0;
|
135
227
|
int i = 0;
|
@@ -153,13 +245,61 @@ VALUE intersys_query_fetch(VALUE self) {
|
|
153
245
|
for(i = 0; i < num_cols; i++) {
|
154
246
|
rb_ary_push(data, rb_funcall(self, rb_intern("get_data"), 1, INT2FIX(i+1)));
|
155
247
|
}
|
156
|
-
rb_funcall(self, rb_intern("close"), 0);
|
157
248
|
return data;
|
158
249
|
}
|
159
250
|
|
251
|
+
|
252
|
+
VALUE intersys_query_each(VALUE self) {
|
253
|
+
struct rbQuery* query;
|
254
|
+
int i;
|
255
|
+
Data_Get_Struct(self, struct rbQuery, query);
|
256
|
+
if(query->offset > 0) {
|
257
|
+
RUN(cbind_query_skip(query->query, query->offset));
|
258
|
+
}
|
259
|
+
for(i = query->offset; i < query->offset + query->limit; i++) {
|
260
|
+
VALUE row = intersys_query_fetch(self);
|
261
|
+
if(row == Qnil || RARRAY(row)->len == 0) {
|
262
|
+
break;
|
263
|
+
}
|
264
|
+
rb_yield(row);
|
265
|
+
}
|
266
|
+
query_close(query);
|
267
|
+
return self;
|
268
|
+
}
|
269
|
+
|
270
|
+
|
271
|
+
|
160
272
|
VALUE intersys_query_close(VALUE self) {
|
161
273
|
struct rbQuery* query;
|
162
274
|
Data_Get_Struct(self, struct rbQuery, query);
|
163
275
|
query_close(query);
|
164
276
|
return self;
|
165
277
|
}
|
278
|
+
|
279
|
+
|
280
|
+
VALUE intersys_query_set_limit(VALUE self, VALUE limit) {
|
281
|
+
struct rbQuery* query;
|
282
|
+
Data_Get_Struct(self, struct rbQuery, query);
|
283
|
+
query->limit = NUM2INT(rb_funcall(limit, rb_intern("to_i"), 0));
|
284
|
+
return limit;
|
285
|
+
}
|
286
|
+
VALUE intersys_query_get_limit(VALUE self) {
|
287
|
+
struct rbQuery* query;
|
288
|
+
Data_Get_Struct(self, struct rbQuery, query);
|
289
|
+
return INT2FIX(query->limit);
|
290
|
+
}
|
291
|
+
|
292
|
+
VALUE intersys_query_set_offset(VALUE self, VALUE offset) {
|
293
|
+
struct rbQuery* query;
|
294
|
+
Data_Get_Struct(self, struct rbQuery, query);
|
295
|
+
query->offset = NUM2INT(rb_funcall(offset, rb_intern("to_i"), 0));
|
296
|
+
return offset;
|
297
|
+
}
|
298
|
+
|
299
|
+
VALUE intersys_query_get_offset(VALUE self) {
|
300
|
+
struct rbQuery* query;
|
301
|
+
Data_Get_Struct(self, struct rbQuery, query);
|
302
|
+
return INT2FIX(query->offset);
|
303
|
+
}
|
304
|
+
|
305
|
+
#endif /* HAVE_SQL_H */
|