intersys 0.1 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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
@@ -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 */