slim-attributes 0.4.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -5,7 +5,7 @@ This is a small patch to the ActiveRecord Mysql adaptor that stops rails from us
5
5
 
6
6
  It is faster, and uses less memory.
7
7
 
8
- Measuring with just ActiveRecord code - fetching stuff from the database - we see anything up to a 50% (or more) speed increase, but I suppose it really depends on your system and environment, and what you are doing with the results from the database. Measure your own system and send me the results!
8
+ Measuring with just ActiveRecord code - fetching stuff from the database - we see anything up to a 50% (or more) speed increase, but I suppose it really depends on your system and environment, and what you are doing with the results from the database. The more columns your tables have, the better the improvement will likely be. Measure your own system and send me the results!
9
9
 
10
10
 
11
11
  Installation
@@ -38,7 +38,7 @@ The field contents are then instantiated into Ruby strings on demand - ruby stri
38
38
 
39
39
  ===========
40
40
 
41
- No warranty - this plugin likely needs some more work if you want it to be foolproof. However, that said, we are using it in our production environment with good results.
42
-
41
+ No warranty - this gem has been tested, but not in all environments. However, that said, we are using it in our production environment with good results.
42
+ Please report bugs to sdsykes at symbol gmail pip com.
43
43
 
44
44
  Copyright (c) 2008 Stephen Sykes, released under the MIT license
@@ -1,3 +1,6 @@
1
+ // Author: Stephen Sykes
2
+ // http://pennysmalls.com
3
+
1
4
  #include "ruby.h"
2
5
  #include "st.h"
3
6
 
@@ -10,6 +13,10 @@
10
13
  #define GetCharStarPtr(obj) (Check_Type(obj, T_DATA), (char**)DATA_PTR(obj))
11
14
 
12
15
  VALUE cRowHash, cClass;
16
+ ID pointers_id, row_info_id, field_indexes_id, real_hash_id, to_hash_id;
17
+
18
+ #define MAX_CACHED_COLUMN_IDS 40
19
+ ID column_ids[MAX_CACHED_COLUMN_IDS];
13
20
 
14
21
  // from mysql/ruby
15
22
  struct mysql_res {
@@ -21,8 +28,16 @@ struct mysql_res {
21
28
  #define SLIM_IS_NULL (char)0x01
22
29
  #define SLIM_IS_SET (char)0x02
23
30
 
24
- #define GET_COL_IV_NAME(str, col_number) sprintf(str, "@col_%d", col_number)
31
+ #define GET_COL_IV_ID(str, cnum) (cnum < MAX_CACHED_COLUMN_IDS ? column_ids[cnum] : (sprintf(str, "@col_%ld", cnum), rb_intern(str)))
25
32
 
33
+ #define REAL_HASH_EXISTS NIL_P(field_indexes = rb_ivar_get(obj, field_indexes_id))
34
+
35
+ // This replaces the usual all_hashes method defined in mysql_adaptor.rb
36
+ //
37
+ // It copies the data from the mysql result into allocated memory
38
+ // ready for creating ruby strings from on demand, instead of creating
39
+ // all the data into ruby strings immediately.
40
+ // all_hashes returns an array of result rows
26
41
  static VALUE all_hashes(VALUE obj) {
27
42
  MYSQL_RES *res = GetMysqlRes(obj);
28
43
  MYSQL_FIELD *fields = mysql_fetch_fields(res);
@@ -34,111 +49,154 @@ static VALUE all_hashes(VALUE obj) {
34
49
  unsigned long *lengths;
35
50
  char *row_info_space, **pointers_space, *p;
36
51
 
37
- /* hash of column names */
52
+ // make a hash of column names
38
53
  col_names_hsh = rb_hash_new();
39
54
  for (i=0; i < nf; i++) {
40
55
  rb_hash_aset(col_names_hsh, rb_str_new2(fields[i].name), INT2FIX(i));
41
56
  }
42
57
 
43
- /* array of result rows */
58
+ // make the array of result rows
44
59
  all_hashes_ary = rb_ary_new2(nr);
60
+
45
61
  for (i=0; i < nr; i++) {
46
- row = mysql_fetch_row(res); // get the row
47
- lengths = mysql_fetch_lengths(res); // get lengths
62
+ row = mysql_fetch_row(res); // get the row data and lengths from mysql
63
+ lengths = mysql_fetch_lengths(res);
48
64
  for (s=j=0; j < nf; j++) s += lengths[j]; // s = total of lengths
49
- pointers_space = malloc((nf + 1) * sizeof(char *) + s); // storage for pointers to data followed by data
65
+ pointers_space = ruby_xmalloc((nf + 1) * sizeof(char *) + s); // space for data pointers & data
50
66
  p = *pointers_space = (char *)(pointers_space + nf + 1); // pointer to first data item
51
- row_info_space = calloc(nf, 1);
67
+ row_info_space = ruby_xcalloc(nf, 1); // space for flags for each column in this row
52
68
  for (j=0; j < nf; j++) {
53
69
  len = (unsigned int)lengths[j];
54
- if (!row[j]) row_info_space[j] = SLIM_IS_NULL;
55
- else memcpy(p, row[j], len); // copy row data in
56
- pointers_space[j + 1] = p += len;
70
+ if (len) {
71
+ memcpy(p, row[j], len); // copy row data in
72
+ p += len;
73
+ } else if (!row[j]) row_info_space[j] = SLIM_IS_NULL; // flag so we can handle null
74
+ pointers_space[j + 1] = p;
57
75
  }
58
- frh = rb_class_new_instance(0, NULL, cRowHash);
59
- rb_iv_set(frh, "@pointers", Data_Wrap_Struct(cClass, 0, free, pointers_space));
60
- rb_iv_set(frh, "@row_info", Data_Wrap_Struct(cClass, 0, free, row_info_space));
61
- rb_iv_set(frh, "@field_indexes", col_names_hsh);
62
- rb_ary_store(all_hashes_ary, i, frh);
76
+ frh = rb_class_new_instance(0, NULL, cRowHash); // create the row object
77
+ rb_ivar_set(frh, pointers_id, Data_Wrap_Struct(cClass, 0, ruby_xfree, pointers_space));
78
+ rb_ivar_set(frh, row_info_id, Data_Wrap_Struct(cClass, 0, ruby_xfree, row_info_space));
79
+ rb_ivar_set(frh, field_indexes_id, col_names_hsh);
80
+ rb_ary_store(all_hashes_ary, i, frh); // store it in the array
63
81
  }
64
82
  return all_hashes_ary;
65
83
  }
66
84
 
85
+ // This does the actual work of creating a ruby string when one is demanded, typically through
86
+ // a call to an active record model property method.
67
87
  static VALUE fetch_by_index(VALUE obj, VALUE index) {
68
88
  VALUE contents;
69
89
  char *row_info, **pointers, *start, col_name[16];
90
+ ID col_id;
70
91
  long col_number = FIX2LONG(index);
71
92
  unsigned int length;
72
-
73
- row_info = GetCharPtr(rb_iv_get(obj, "@row_info")) + col_number;
93
+ row_info = GetCharPtr(rb_ivar_get(obj, row_info_id)) + col_number; // flags for this column
74
94
  if (*row_info == SLIM_IS_NULL) return Qnil; // return nil if null from db
75
- GET_COL_IV_NAME(col_name, col_number);
76
- if (*row_info == SLIM_IS_SET) return rb_iv_get(obj, col_name); // was set already, return array entry
77
-
78
- pointers = GetCharStarPtr(rb_iv_get(obj, "@pointers"));
95
+ col_id = GET_COL_IV_ID(col_name, col_number);
96
+ if (*row_info == SLIM_IS_SET) return rb_ivar_get(obj, col_id); // was made to a string already
97
+ pointers = GetCharStarPtr(rb_ivar_get(obj, pointers_id)); // find the data and make ruby string
79
98
  start = pointers[col_number];
80
99
  length = pointers[col_number + 1] - start;
81
100
  contents = rb_tainted_str_new(start, length);
82
- rb_iv_set(obj, col_name, contents);
101
+ rb_ivar_set(obj, col_id, contents); // it is efficient to save the string in an instance variable
83
102
  *row_info = SLIM_IS_SET;
84
103
  return contents;
85
104
  }
86
105
 
106
+ // This is the [] method of the row data object.
107
+ // It checks for a real hash, but if none exists it will call fetch_by_index
87
108
  static VALUE slim_fetch(VALUE obj, VALUE name) {
88
- VALUE real_hash, hash_lookup;
89
- real_hash = rb_iv_get(obj, "@real_hash");
90
- if (!NIL_P(real_hash)) return rb_hash_aref(real_hash, name);
91
- hash_lookup = rb_hash_aref(rb_iv_get(obj, "@field_indexes"), name);
109
+ VALUE field_indexes, hash_lookup;
110
+
111
+ if (REAL_HASH_EXISTS) return rb_hash_aref(rb_ivar_get(obj, real_hash_id), name);
112
+
113
+ hash_lookup = rb_hash_aref(field_indexes, name);
92
114
  if (NIL_P(hash_lookup)) return Qnil;
93
115
  return fetch_by_index(obj, hash_lookup);
94
116
  }
95
117
 
118
+ // This is the []= method of the row data object.
119
+ // It either operates on the real hash if it exists, or sets the appropriate
120
+ // column instance variable
96
121
  static VALUE set_element(VALUE obj, VALUE name, VALUE val) {
97
- VALUE real_hash, hash_lookup;
122
+ VALUE field_indexes, hash_lookup;
98
123
  long col_number;
99
124
  char col_name[16];
125
+ ID col_id;
100
126
 
101
- real_hash = rb_iv_get(obj, "@real_hash");
102
- if (!NIL_P(real_hash)) return rb_hash_aset(real_hash, name, val);
127
+ if (REAL_HASH_EXISTS) return rb_hash_aset(rb_ivar_get(obj, real_hash_id), name, val);
103
128
 
104
- hash_lookup = rb_hash_aref(rb_iv_get(obj, "@field_indexes"), name);
105
- if (NIL_P(hash_lookup)) return rb_funcall(rb_funcall(obj, rb_intern("to_hash"), 0), rb_intern("[]="), 2, name, val);
129
+ hash_lookup = rb_hash_aref(field_indexes, name);
130
+ if (NIL_P(hash_lookup)) return rb_hash_aset(rb_funcall(obj, to_hash_id, 0), name, val);
106
131
  col_number = FIX2LONG(hash_lookup);
107
- GET_COL_IV_NAME(col_name, col_number);
108
- rb_iv_set(obj, col_name, val);
109
- GetCharPtr(rb_iv_get(obj, "@row_info"))[col_number] = SLIM_IS_SET;
132
+ col_id = GET_COL_IV_ID(col_name, col_number);
133
+ rb_ivar_set(obj, col_id, val);
134
+ GetCharPtr(rb_ivar_get(obj, row_info_id))[col_number] = SLIM_IS_SET;
110
135
  return val;
111
136
  }
112
137
 
138
+ // This is the dup method of the row data object.
139
+ // When the query cache is used, the result of all_hashes is dupped before
140
+ // being passed back to the user.
141
+ // Subsequent queries of the same SQL will get another dup of the results.
142
+ // So we must implement dup in an efficient way (without converting to a real hash).
143
+ //
144
+ // Note: this method currently ignores any columns that have been assigned to using
145
+ // []= before calling dup (the original values will be seen in the dup). This works ok
146
+ // for active record usage, but perhaps could cause unexpected behaviour if model
147
+ // attributes are dupped by the user after changing them.
113
148
  static VALUE dup(VALUE obj) {
114
- VALUE real_hash, frh, field_indexes;
149
+ VALUE frh, field_indexes;
115
150
  int nf, i;
116
- char *row_info_space, col_name[16];
117
-
118
- real_hash = rb_iv_get(obj, "@real_hash");
119
- if (!NIL_P(real_hash)) return rb_obj_dup(real_hash);
151
+ char *row_info_space;
152
+
153
+ if (REAL_HASH_EXISTS) return rb_obj_dup(rb_ivar_get(obj, real_hash_id));
120
154
 
121
- field_indexes = rb_iv_get(obj, "@field_indexes");
122
155
  nf = RHASH(field_indexes)->tbl->num_entries;
123
- row_info_space = malloc(nf);
124
- memcpy(row_info_space, GetCharPtr(rb_iv_get(obj, "@row_info")), nf);
156
+ row_info_space = ruby_xmalloc(nf); // dup needs its own set of flags
157
+ memcpy(row_info_space, GetCharPtr(rb_ivar_get(obj, row_info_id)), nf);
125
158
  for (i=0; i < nf; i++) row_info_space[i] &= ~SLIM_IS_SET; // remove any set flags
126
- frh = rb_class_new_instance(0, NULL, cRowHash);
127
- rb_iv_set(frh, "@pointers", rb_iv_get(obj, "@pointers"));
128
- rb_iv_set(frh, "@row_info", Data_Wrap_Struct(cClass, 0, free, row_info_space));
129
- rb_iv_set(frh, "@field_indexes", field_indexes);
159
+ frh = rb_class_new_instance(0, NULL, cRowHash); // make the new row data object
160
+ rb_ivar_set(frh, pointers_id, rb_ivar_get(obj, pointers_id));
161
+ rb_ivar_set(frh, row_info_id, Data_Wrap_Struct(cClass, 0, ruby_xfree, row_info_space));
162
+ rb_ivar_set(frh, field_indexes_id, field_indexes);
130
163
  return frh;
131
164
  }
132
165
 
166
+ // This is the has_key? method of the row data object.
167
+ // Calls to model property methods in AR cause a call to has_key?, so it
168
+ // is implemented here in C for speed.
169
+ static VALUE has_key(VALUE obj, VALUE name) {
170
+ VALUE field_indexes;
171
+
172
+ if (REAL_HASH_EXISTS) return (st_lookup(RHASH(rb_ivar_get(obj, real_hash_id))->tbl, name, 0) ? Qtrue : Qfalse);
173
+ else return (st_lookup(RHASH(field_indexes)->tbl, name, 0) ? Qtrue : Qfalse);
174
+ }
175
+
133
176
  void Init_slim_attrib_ext() {
177
+ int i;
178
+ char col_name[16];
134
179
  VALUE c = rb_cObject;
180
+
135
181
  c = rb_const_get_at(c, rb_intern("Mysql"));
136
182
  c = rb_const_get_at(c, rb_intern("Result"));
137
183
  rb_define_method(c, "all_hashes", (VALUE(*)(ANYARGS))all_hashes, 0);
138
184
  cRowHash = rb_const_get_at(c, rb_intern("RowHash"));
139
185
  cClass = rb_define_class("CObjects", cRowHash);
186
+ // set up methods
140
187
  rb_define_private_method(cRowHash, "fetch_by_index", (VALUE(*)(ANYARGS))fetch_by_index, 1);
141
188
  rb_define_method(cRowHash, "[]", (VALUE(*)(ANYARGS))slim_fetch, 1);
142
189
  rb_define_method(cRowHash, "[]=", (VALUE(*)(ANYARGS))set_element, 2);
143
190
  rb_define_method(cRowHash, "dup", (VALUE(*)(ANYARGS))dup, 0);
191
+ rb_define_method(cRowHash, "has_key?", (VALUE(*)(ANYARGS))has_key, 1);
192
+ // set up some symbols that we will need
193
+ pointers_id = rb_intern("@pointers");
194
+ row_info_id = rb_intern("@row_info");
195
+ field_indexes_id = rb_intern("@field_indexes");
196
+ real_hash_id = rb_intern("@real_hash");
197
+ to_hash_id = rb_intern("to_hash");
198
+ for(i=0; i < MAX_CACHED_COLUMN_IDS; i++) {
199
+ sprintf(col_name, "@col_%d", i);
200
+ column_ids[i] = rb_intern(col_name);
201
+ }
144
202
  }
@@ -1,5 +1,12 @@
1
+ # Author: Stephen Sykes
2
+ # http://pennysmalls.com
3
+
1
4
  require 'mysql'
2
5
 
6
+ class Mysql::Result; class RowHash; end; end
7
+
8
+ require 'slim_attrib_ext'
9
+
3
10
  class Mysql::Result
4
11
  class RowHash
5
12
  def marshal_dump
@@ -10,18 +17,22 @@ class Mysql::Result
10
17
  @real_hash = hash
11
18
  end
12
19
 
13
- def has_key?(name)
14
- @real_hash ? @real_hash.has_key?(name) : @field_indexes[name]
15
- end
16
-
17
20
  alias_method :include?, :has_key?
18
21
 
19
22
  def keys
20
23
  @real_hash ? @real_hash.keys : @field_indexes.keys
21
24
  end
22
25
 
26
+ # If you want to do anything other than [], []=, dup, keys and has_key? then
27
+ # we'll handle that by doing the operation on a real ruby hash.
28
+ # This should be the exception though, and the efficiencies of using slim-attributes
29
+ # are lost when this happens.
23
30
  def to_hash
24
- @real_hash ||= @field_indexes.inject({}) {|memo, fi| memo[fi[0]] = fetch_by_index(fi[1]); memo}
31
+ return @real_hash if @real_hash
32
+ @real_hash = {}
33
+ @field_indexes.each_pair {|name, index| @real_hash[name] = fetch_by_index(index)}
34
+ @field_indexes = nil
35
+ @real_hash
25
36
  end
26
37
 
27
38
  def to_a
@@ -41,5 +52,3 @@ class Mysql::Result
41
52
  end
42
53
  end
43
54
  end
44
-
45
- require 'slim_attrib_ext'
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.4
3
3
  specification_version: 1
4
4
  name: slim-attributes
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.4.1
7
- date: 2008-05-18 00:00:00 +03:00
6
+ version: 0.5.0
7
+ date: 2008-10-14 00:00:00 +03:00
8
8
  summary: Slim attributes boosts speed in Rails/Mysql ActiveRecord Models by avoiding instantiating Hashes for each result row, and lazily instantiating attributes as needed
9
9
  require_paths:
10
10
  - lib