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 +3 -3
- data/ext/slim_attrib_ext.c +104 -46
- data/lib/slim_attributes.rb +16 -7
- metadata +2 -2
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
|
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
|
data/ext/slim_attrib_ext.c
CHANGED
@@ -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
|
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
|
-
|
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
|
-
|
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);
|
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 =
|
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 =
|
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 (
|
55
|
-
|
56
|
-
|
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
|
-
|
60
|
-
|
61
|
-
|
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
|
-
|
76
|
-
if (*row_info == SLIM_IS_SET) return
|
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
|
-
|
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
|
89
|
-
|
90
|
-
if (
|
91
|
-
|
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
|
122
|
+
VALUE field_indexes, hash_lookup;
|
98
123
|
long col_number;
|
99
124
|
char col_name[16];
|
125
|
+
ID col_id;
|
100
126
|
|
101
|
-
|
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(
|
105
|
-
if (NIL_P(hash_lookup)) return
|
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
|
-
|
108
|
-
|
109
|
-
GetCharPtr(
|
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
|
149
|
+
VALUE frh, field_indexes;
|
115
150
|
int nf, i;
|
116
|
-
char *row_info_space
|
117
|
-
|
118
|
-
|
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 =
|
124
|
-
memcpy(row_info_space, GetCharPtr(
|
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
|
-
|
128
|
-
|
129
|
-
|
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
|
}
|
data/lib/slim_attributes.rb
CHANGED
@@ -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
|
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.
|
7
|
-
date: 2008-
|
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
|