intersys 0.1 → 0.2
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.
- 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 */
|