panko_serializer 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.clang-format +102 -0
  3. data/.gitignore +12 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +22 -0
  6. data/.travis.yml +8 -0
  7. data/Gemfile +36 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +8 -0
  10. data/Rakefile +62 -0
  11. data/benchmarks/BENCHMARKS.md +48 -0
  12. data/benchmarks/allocs.rb +23 -0
  13. data/benchmarks/app.rb +13 -0
  14. data/benchmarks/benchmarking_support.rb +43 -0
  15. data/benchmarks/bm_active_model_serializers.rb +45 -0
  16. data/benchmarks/bm_controller.rb +81 -0
  17. data/benchmarks/bm_panko_json.rb +60 -0
  18. data/benchmarks/bm_panko_object.rb +69 -0
  19. data/benchmarks/profile.rb +88 -0
  20. data/benchmarks/sanity.rb +67 -0
  21. data/benchmarks/setup.rb +62 -0
  22. data/benchmarks/type_casts/bm_active_record.rb +57 -0
  23. data/benchmarks/type_casts/bm_panko.rb +67 -0
  24. data/benchmarks/type_casts/bm_pg.rb +35 -0
  25. data/benchmarks/type_casts/support.rb +16 -0
  26. data/ext/panko_serializer/attributes_iterator.c +62 -0
  27. data/ext/panko_serializer/attributes_iterator.h +17 -0
  28. data/ext/panko_serializer/extconf.rb +8 -0
  29. data/ext/panko_serializer/panko_serializer.c +189 -0
  30. data/ext/panko_serializer/panko_serializer.h +17 -0
  31. data/ext/panko_serializer/serialization_descriptor.c +166 -0
  32. data/ext/panko_serializer/serialization_descriptor.h +30 -0
  33. data/ext/panko_serializer/time_conversion.c +94 -0
  34. data/ext/panko_serializer/time_conversion.h +6 -0
  35. data/ext/panko_serializer/type_cast.c +271 -0
  36. data/ext/panko_serializer/type_cast.h +74 -0
  37. data/lib/panko/array_serializer.rb +40 -0
  38. data/lib/panko/cache.rb +35 -0
  39. data/lib/panko/serialization_descriptor.rb +119 -0
  40. data/lib/panko/serializer.rb +57 -0
  41. data/lib/panko/version.rb +4 -0
  42. data/lib/panko.rb +8 -0
  43. data/panko_serializer.gemspec +31 -0
  44. metadata +171 -0
@@ -0,0 +1,166 @@
1
+ #include "serialization_descriptor.h"
2
+
3
+ VALUE cSerializationDescriptor;
4
+
5
+ static ID context_id = 0;
6
+ static ID object_id = 0;
7
+
8
+ static void serialization_descriptor_free(void* ptr) {
9
+ if (ptr == 0) {
10
+ return;
11
+ }
12
+
13
+ SerializationDescriptor sd = (SerializationDescriptor)ptr;
14
+ sd->serializer_type = Qnil;
15
+ sd->serializer = Qnil;
16
+ sd->fields = Qnil;
17
+ sd->method_fields = Qnil;
18
+ sd->has_one_associations = Qnil;
19
+ sd->has_many_associations = Qnil;
20
+ }
21
+
22
+ void serialization_descriptor_mark(SerializationDescriptor data) {
23
+ rb_gc_mark(data->serializer_type);
24
+ rb_gc_mark(data->serializer);
25
+ rb_gc_mark(data->fields);
26
+ rb_gc_mark(data->method_fields);
27
+ rb_gc_mark(data->has_one_associations);
28
+ rb_gc_mark(data->has_many_associations);
29
+ }
30
+
31
+ static VALUE serialization_descriptor_new(int argc, VALUE* argv, VALUE self) {
32
+ SerializationDescriptor sd = ALLOC(struct _SerializationDescriptor);
33
+
34
+ sd->serializer = Qnil;
35
+ sd->serializer_type = Qnil;
36
+ sd->fields = Qnil;
37
+ sd->method_fields = Qnil;
38
+ sd->has_one_associations = Qnil;
39
+ sd->has_many_associations = Qnil;
40
+
41
+ return Data_Wrap_Struct(cSerializationDescriptor,
42
+ serialization_descriptor_mark,
43
+ serialization_descriptor_free, sd);
44
+ }
45
+
46
+ SerializationDescriptor sd_read(VALUE descriptor) {
47
+ return (SerializationDescriptor)DATA_PTR(descriptor);
48
+ }
49
+
50
+ VALUE sd_build_serializer(SerializationDescriptor sd) {
51
+ // We build the serializer and cache it on demand,
52
+ // because of our cache - we lock and create descriptor, while inside
53
+ // a descriptor we can't create another descriptor - deadlock.
54
+ if (sd->serializer == Qnil) {
55
+ VALUE args[0];
56
+ sd->serializer = rb_class_new_instance(0, args, sd->serializer_type);
57
+ }
58
+
59
+ return sd->serializer;
60
+ }
61
+
62
+ void sd_apply_serializer_config(VALUE serializer, VALUE object, VALUE context) {
63
+ rb_ivar_set(serializer, object_id, object);
64
+ rb_ivar_set(serializer, context_id, context);
65
+ }
66
+
67
+
68
+ VALUE serialization_descriptor_fields_set(VALUE self, VALUE fields) {
69
+ SerializationDescriptor sd = (SerializationDescriptor)DATA_PTR(self);
70
+
71
+ sd->fields = fields;
72
+ return Qnil;
73
+ }
74
+
75
+ VALUE serialization_descriptor_fields_ref(VALUE self) {
76
+ SerializationDescriptor sd = (SerializationDescriptor)DATA_PTR(self);
77
+ return sd->fields;
78
+ }
79
+
80
+ VALUE serialization_descriptor_method_fields_set(VALUE self,
81
+ VALUE method_fields) {
82
+ SerializationDescriptor sd = (SerializationDescriptor)DATA_PTR(self);
83
+ sd->method_fields = method_fields;
84
+ return Qnil;
85
+ }
86
+
87
+ VALUE serialization_descriptor_method_fields_ref(VALUE self) {
88
+ SerializationDescriptor sd = (SerializationDescriptor)DATA_PTR(self);
89
+ return sd->method_fields;
90
+ }
91
+
92
+ VALUE serialization_descriptor_has_one_associations_set(
93
+ VALUE self,
94
+ VALUE has_one_associations) {
95
+ SerializationDescriptor sd = (SerializationDescriptor)DATA_PTR(self);
96
+ sd->has_one_associations = has_one_associations;
97
+ return Qnil;
98
+ }
99
+
100
+ VALUE serialization_descriptor_has_one_associations_ref(VALUE self) {
101
+ SerializationDescriptor sd = (SerializationDescriptor)DATA_PTR(self);
102
+ return sd->has_one_associations;
103
+ }
104
+
105
+ VALUE serialization_descriptor_has_many_associations_set(
106
+ VALUE self,
107
+ VALUE has_many_associations) {
108
+ SerializationDescriptor sd = (SerializationDescriptor)DATA_PTR(self);
109
+ sd->has_many_associations = has_many_associations;
110
+ return Qnil;
111
+ }
112
+
113
+ VALUE serialization_descriptor_has_many_associations_ref(VALUE self) {
114
+ SerializationDescriptor sd = (SerializationDescriptor)DATA_PTR(self);
115
+ return sd->has_many_associations;
116
+ }
117
+
118
+ VALUE serialization_descriptor_type_set(VALUE self, VALUE type) {
119
+ SerializationDescriptor sd = (SerializationDescriptor)DATA_PTR(self);
120
+ sd->serializer_type = type;
121
+ return Qnil;
122
+ }
123
+
124
+ // Exposing this for testing
125
+ VALUE serialization_descriptor_build_serializer(VALUE self) {
126
+ SerializationDescriptor sd = (SerializationDescriptor)DATA_PTR(self);
127
+ return sd_build_serializer(sd);
128
+ }
129
+
130
+ void panko_init_serialization_descriptor(VALUE mPanko) {
131
+ CONST_ID(object_id, "@object");
132
+ CONST_ID(context_id, "@context");
133
+
134
+ cSerializationDescriptor =
135
+ rb_define_class_under(mPanko, "SerializationDescriptor", rb_cObject);
136
+
137
+ rb_define_module_function(cSerializationDescriptor, "new",
138
+ serialization_descriptor_new, -1);
139
+
140
+ rb_define_method(cSerializationDescriptor,
141
+ "fields=", serialization_descriptor_fields_set, 1);
142
+ rb_define_method(cSerializationDescriptor, "fields",
143
+ serialization_descriptor_fields_ref, 0);
144
+
145
+ rb_define_method(cSerializationDescriptor,
146
+ "method_fields=", serialization_descriptor_method_fields_set,
147
+ 1);
148
+ rb_define_method(cSerializationDescriptor, "method_fields",
149
+ serialization_descriptor_method_fields_ref, 0);
150
+
151
+ rb_define_method(cSerializationDescriptor, "has_one_associations=",
152
+ serialization_descriptor_has_one_associations_set, 1);
153
+ rb_define_method(cSerializationDescriptor, "has_one_associations",
154
+ serialization_descriptor_has_one_associations_ref, 0);
155
+
156
+ rb_define_method(cSerializationDescriptor, "has_many_associations=",
157
+ serialization_descriptor_has_many_associations_set, 1);
158
+ rb_define_method(cSerializationDescriptor, "has_many_associations",
159
+ serialization_descriptor_has_many_associations_ref, 0);
160
+
161
+ rb_define_method(cSerializationDescriptor,
162
+ "type=", serialization_descriptor_type_set, 1);
163
+
164
+ rb_define_method(cSerializationDescriptor, "build_serializer",
165
+ serialization_descriptor_build_serializer, 0);
166
+ }
@@ -0,0 +1,30 @@
1
+ #include <ruby.h>
2
+
3
+ #ifndef __SD_H__
4
+ #define __SD_H__
5
+
6
+ typedef struct _SerializationDescriptor {
7
+ // type of the serializer, so we can create it later
8
+ VALUE serializer_type;
9
+ // Cached value of the serializer
10
+ VALUE serializer;
11
+
12
+ // Metadata
13
+ VALUE fields;
14
+ VALUE method_fields;
15
+ VALUE has_one_associations;
16
+ VALUE has_many_associations;
17
+ } * SerializationDescriptor;
18
+
19
+ VALUE serialization_descriptor_fields_ref(VALUE descriptor);
20
+ VALUE serialization_descriptor_method_fields_ref(VALUE descriptor);
21
+ VALUE serialization_descriptor_has_one_associations_ref(VALUE descriptor);
22
+ VALUE serialization_descriptor_has_many_associations_ref(VALUE descriptor);
23
+
24
+ SerializationDescriptor sd_read(VALUE descriptor);
25
+ VALUE sd_build_serializer(SerializationDescriptor descriptor);
26
+ void sd_apply_serializer_config(VALUE serializer, VALUE object, VALUE context);
27
+
28
+ void panko_init_serialization_descriptor(VALUE mPanko);
29
+
30
+ #endif
@@ -0,0 +1,94 @@
1
+ #include "time_conversion.h"
2
+
3
+ static regex_t* iso8601_time_regex;
4
+ static regex_t* ar_iso_datetime_regex;
5
+
6
+ VALUE is_iso8601_time_string(const char* value) {
7
+ const UChar *start, *range, *end;
8
+
9
+ const UChar* str = (const UChar*)(value);
10
+
11
+ end = str + strlen(value);
12
+ start = str;
13
+ range = end;
14
+ OnigPosition r = onig_search(iso8601_time_regex, str, end, start, range, NULL,
15
+ ONIG_OPTION_NONE);
16
+
17
+ return r >= 0 ? Qtrue : Qfalse;
18
+ }
19
+
20
+ void append_region_part(char* buf,
21
+ const char* value,
22
+ const OnigRegion* region,
23
+ int i) {
24
+ long substringLength = region->end[i] - region->beg[i];
25
+ strncat(buf, value + region->beg[i], substringLength);
26
+ }
27
+
28
+ VALUE iso_ar_iso_datetime_string(const char* value) {
29
+ const UChar *start, *range, *end;
30
+ OnigRegion* region = onig_region_new();
31
+
32
+ const UChar* str = (const UChar*)(value);
33
+
34
+ end = str + strlen(value);
35
+ start = str;
36
+ range = end;
37
+ OnigPosition r = onig_search(ar_iso_datetime_regex, str, end, start, range,
38
+ region, ONIG_OPTION_NONE);
39
+
40
+ VALUE output = Qnil;
41
+ if (r >= 0) {
42
+ char buf[21];
43
+ sprintf(buf, "");
44
+
45
+ append_region_part(buf, value, region, 1);
46
+ strncat(buf, "-", 1);
47
+
48
+ append_region_part(buf, value, region, 2);
49
+ strncat(buf, "-", 1);
50
+
51
+ append_region_part(buf, value, region, 3);
52
+ strncat(buf, "T", 1);
53
+
54
+ append_region_part(buf, value, region, 4);
55
+ strncat(buf, ":", 1);
56
+
57
+ append_region_part(buf, value, region, 5);
58
+ strncat(buf, ":", 1);
59
+
60
+ append_region_part(buf, value, region, 6);
61
+ strncat(buf, "Z", 1);
62
+
63
+ output = rb_str_new(buf, strlen(buf));
64
+ }
65
+
66
+ onig_region_free(region, 1);
67
+ return output;
68
+ }
69
+
70
+ void build_regex(OnigRegex* reg, const UChar* pattern) {
71
+ OnigErrorInfo einfo;
72
+
73
+ int r = onig_new(reg, pattern, pattern + strlen((char*)pattern),
74
+ ONIG_OPTION_DEFAULT, ONIG_ENCODING_ASCII,
75
+ ONIG_SYNTAX_DEFAULT, &einfo);
76
+
77
+ if (r != ONIG_NORMAL) {
78
+ char s[ONIG_MAX_ERROR_MESSAGE_LEN];
79
+ onig_error_code_to_str((UChar*)s, r, &einfo);
80
+ printf("ERROR: %s\n", s);
81
+ }
82
+ }
83
+
84
+ void panko_init_time(VALUE mPanko) {
85
+ const UChar* ISO8601_PATTERN =
86
+ (UChar*)"^([\\+-]?\\d{4}(?!\\d{2}\\b))((-?)((0[1-9]|1[0-2])(\\3([12]\\d|0[1-9]|3[01]))?|W([0-4]\\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\\d|[12]\\d{2}|3([0-5]\\d|6[1-6])))([T\\s]((([01]\\d|2[0-3])((:?)[0-5]\\d)?|24\\:?00)([\\.,]\\d+(?!:))?)?(\\17[0-5]\\d([\\.,]\\d+)?)?([zZ]|([\\+-])([01]\\d|2[0-3]):?([0-5]\\d)?)?)?)?$";
87
+
88
+ build_regex(&iso8601_time_regex, ISO8601_PATTERN);
89
+
90
+ const UChar* AR_ISO_DATETIME_PATTERN =
91
+ (UChar*)"\\A(?<year>\\d{4})-(?<month>\\d\\d)-(?<mday>\\d\\d) (?<hour>\\d\\d):(?<min>\\d\\d):(?<sec>\\d\\d)(\\.(?<microsec>\\d+))?\\z";
92
+
93
+ build_regex(&ar_iso_datetime_regex, AR_ISO_DATETIME_PATTERN);
94
+ }
@@ -0,0 +1,6 @@
1
+ #include <ruby.h>
2
+ #include <ruby/oniguruma.h>
3
+
4
+ VALUE is_iso8601_time_string(const char* value);
5
+ VALUE iso_ar_iso_datetime_string(const char* value);
6
+ void panko_init_time(VALUE mPanko);
@@ -0,0 +1,271 @@
1
+ #include "type_cast.h"
2
+ #include "time_conversion.h"
3
+
4
+ static ID type_cast_from_database_id = 0;
5
+ static ID to_s_id = 0;
6
+ static ID to_i_id = 0;
7
+
8
+ // Caching ActiveRecord Types
9
+ static VALUE ar_string_type = Qundef;
10
+ static VALUE ar_text_type = Qundef;
11
+ static VALUE ar_float_type = Qundef;
12
+ static VALUE ar_integer_type = Qundef;
13
+ static VALUE ar_boolean_type = Qundef;
14
+ static VALUE ar_date_time_type = Qundef;
15
+ static VALUE ar_time_zone_converter = Qundef;
16
+
17
+ static VALUE ar_pg_integer_type = Qundef;
18
+ static VALUE ar_pg_float_type = Qundef;
19
+ static VALUE ar_pg_uuid_type = Qundef;
20
+ static VALUE ar_pg_json_type = Qundef;
21
+ static VALUE ar_pg_date_time_type = Qundef;
22
+
23
+ static int initiailized = 0;
24
+
25
+ VALUE cache_postgres_type_lookup(VALUE ar) {
26
+ VALUE ar_connection_adapters =
27
+ rb_const_get_at(ar, rb_intern("ConnectionAdapters"));
28
+ if (ar_connection_adapters == Qundef) {
29
+ return Qfalse;
30
+ }
31
+
32
+ VALUE ar_postgresql =
33
+ rb_const_get_at(ar_connection_adapters, rb_intern("PostgreSQL"));
34
+ if (ar_postgresql == Qundef) {
35
+ return Qfalse;
36
+ }
37
+
38
+ VALUE ar_oid = rb_const_get_at(ar_postgresql, rb_intern("OID"));
39
+ if (ar_oid == Qundef) {
40
+ return Qfalse;
41
+ }
42
+
43
+ ar_pg_integer_type = rb_const_get_at(ar_oid, rb_intern("Integer"));
44
+ ar_pg_float_type = rb_const_get_at(ar_oid, rb_intern("Float"));
45
+ ar_pg_uuid_type = rb_const_get_at(ar_oid, rb_intern("Uuid"));
46
+ ar_pg_json_type = rb_const_get_at(ar_oid, rb_intern("Json"));
47
+ ar_pg_date_time_type = rb_const_get_at(ar_oid, rb_intern("DateTime"));
48
+
49
+ return Qtrue;
50
+ }
51
+
52
+ VALUE cache_time_zone_type_lookup(VALUE ar) {
53
+ // ActiveRecord::AttributeMethods
54
+ VALUE ar_attr_methods = rb_const_get_at(ar, rb_intern("AttributeMethods"));
55
+ if (ar_attr_methods == Qundef) {
56
+ return Qfalse;
57
+ }
58
+
59
+ // ActiveRecord::AttributeMethods::TimeZoneConversion
60
+ VALUE ar_time_zone_conversion =
61
+ rb_const_get_at(ar_attr_methods, rb_intern("TimeZoneConversion"));
62
+ if (ar_time_zone_conversion == Qundef) {
63
+ return Qfalse;
64
+ }
65
+
66
+ ar_time_zone_converter =
67
+ rb_const_get_at(ar_time_zone_conversion, rb_intern("TimeZoneConverter"));
68
+
69
+ return Qtrue;
70
+ }
71
+
72
+ void cache_type_lookup() {
73
+ if (initiailized == 1) {
74
+ return;
75
+ }
76
+
77
+ initiailized = 1;
78
+
79
+ VALUE ar = rb_const_get_at(rb_cObject, rb_intern("ActiveRecord"));
80
+
81
+ // ActiveRecord::Type
82
+ VALUE ar_type = rb_const_get_at(ar, rb_intern("Type"));
83
+
84
+ ar_string_type = rb_const_get_at(ar_type, rb_intern("String"));
85
+ ar_text_type = rb_const_get_at(ar_type, rb_intern("Text"));
86
+ ar_float_type = rb_const_get_at(ar_type, rb_intern("Float"));
87
+ ar_integer_type = rb_const_get_at(ar_type, rb_intern("Integer"));
88
+ ar_boolean_type = rb_const_get_at(ar_type, rb_intern("Boolean"));
89
+ ar_date_time_type = rb_const_get_at(ar_type, rb_intern("DateTime"));
90
+
91
+ // TODO: if we get error or not, add this to some debug log
92
+ int isErrored;
93
+ rb_protect(cache_postgres_type_lookup, ar, &isErrored);
94
+
95
+ rb_protect(cache_time_zone_type_lookup, ar, &isErrored);
96
+ }
97
+
98
+ bool is_string_or_text_type(VALUE type_klass) {
99
+ return type_klass == ar_string_type || type_klass == ar_text_type ||
100
+ (ar_pg_uuid_type != Qundef && type_klass == ar_pg_uuid_type);
101
+ }
102
+
103
+ VALUE cast_string_or_text_type(VALUE value) {
104
+ if (RB_TYPE_P(value, T_STRING)) {
105
+ return value;
106
+ }
107
+
108
+ if (value == Qtrue) {
109
+ return rb_str_new_cstr("t");
110
+ }
111
+
112
+ if (value == Qfalse) {
113
+ return rb_str_new_cstr("f");
114
+ }
115
+
116
+ return rb_funcall(value, to_s_id, 0);
117
+ }
118
+
119
+ bool is_float_type(VALUE type_klass) {
120
+ return type_klass == ar_float_type ||
121
+ (ar_pg_float_type != Qundef && type_klass == ar_pg_float_type);
122
+ }
123
+
124
+ VALUE cast_float_type(VALUE value) {
125
+ if (RB_TYPE_P(value, T_FLOAT)) {
126
+ return value;
127
+ }
128
+
129
+ if (RB_TYPE_P(value, T_STRING)) {
130
+ const char* val = StringValuePtr(value);
131
+ return rb_float_new(strtod(val, NULL));
132
+ }
133
+
134
+ return Qundef;
135
+ }
136
+
137
+ bool is_integer_type(VALUE type_klass) {
138
+ return type_klass == ar_integer_type ||
139
+ (ar_pg_integer_type != Qundef && type_klass == ar_pg_integer_type);
140
+ }
141
+
142
+ VALUE cast_integer_type(VALUE value) {
143
+ if (RB_INTEGER_TYPE_P(value)) {
144
+ return value;
145
+ }
146
+
147
+ if (RB_TYPE_P(value, T_STRING)) {
148
+ const char* val = StringValuePtr(value);
149
+ if (strlen(val) == 0) {
150
+ return Qnil;
151
+ }
152
+ return rb_cstr2inum(val, 10);
153
+ }
154
+
155
+ if (RB_FLOAT_TYPE_P(value)) {
156
+ // We are calling the `to_i` here, because ruby internal
157
+ // `flo_to_i` is not accessible
158
+ return rb_funcall(value, to_i_id, 0);
159
+ }
160
+
161
+ if (value == Qtrue) {
162
+ return INT2NUM(1);
163
+ }
164
+
165
+ if (value == Qfalse) {
166
+ return INT2NUM(0);
167
+ }
168
+
169
+ // At this point, we handled integer, float, string and booleans
170
+ // any thing other than this (array, hashes, etc) should result in nil
171
+ return Qnil;
172
+ }
173
+
174
+ bool is_json_type(VALUE type_klass) {
175
+ return ar_pg_json_type != Qundef && type_klass == ar_pg_json_type;
176
+ }
177
+
178
+ VALUE cast_json_type(VALUE value) {
179
+ if (!RB_TYPE_P(value, T_STRING)) {
180
+ return value;
181
+ }
182
+
183
+ // TODO: instead of parsing the json, let's signal to "write_value"
184
+ // to use "push_json" instead of "push_value"
185
+ return Qundef;
186
+ }
187
+
188
+ bool is_boolean_type(VALUE type_klass) {
189
+ return type_klass == ar_boolean_type;
190
+ }
191
+
192
+ VALUE cast_boolean_type(VALUE value) {
193
+ if (value == Qtrue || value == Qfalse) {
194
+ return value;
195
+ }
196
+
197
+ if (value == Qnil || RSTRING_LEN(value) == 0) {
198
+ return Qnil;
199
+ }
200
+
201
+ const char* val = StringValuePtr(value);
202
+ bool isFalseValue =
203
+ (*val == '0' || (*val == 'f' || *val == 'F') ||
204
+ (strcmp(val, "false") == 0 || strcmp(val, "FALSE") == 0) ||
205
+ (strcmp(val, "off") == 0 || strcmp(val, "OFF") == 0));
206
+
207
+ return isFalseValue ? Qfalse : Qtrue;
208
+ }
209
+
210
+ bool is_date_time_type(VALUE type_klass) {
211
+ return (type_klass == ar_date_time_type) ||
212
+ (ar_pg_date_time_type != Qundef &&
213
+ type_klass == ar_pg_date_time_type) ||
214
+ (ar_time_zone_converter != Qundef &&
215
+ type_klass == ar_time_zone_converter);
216
+ }
217
+
218
+ VALUE cast_date_time_type(VALUE value) {
219
+ // Instead of take strings to comparing them to time zones
220
+ // and then comparing them back to string
221
+ // We will just make sure we have string on ISO8601 and it's utc
222
+ if (RB_TYPE_P(value, T_STRING)) {
223
+ const char* val = StringValuePtr(value);
224
+ // 'Z' in ISO8601 says it's UTC
225
+ if (val[strlen(val) - 1] == 'Z' && is_iso8601_time_string(val) == Qtrue) {
226
+ return value;
227
+ }
228
+
229
+ VALUE iso8601_string = iso_ar_iso_datetime_string(val);
230
+ if (iso8601_string != Qnil) {
231
+ return iso8601_string;
232
+ }
233
+ }
234
+
235
+ return Qundef;
236
+ }
237
+
238
+ VALUE type_cast(VALUE type_metadata, VALUE value) {
239
+ cache_type_lookup();
240
+
241
+ VALUE type_klass = CLASS_OF(type_metadata);
242
+ VALUE typeCastedValue = Qundef;
243
+
244
+ TypeCast typeCast;
245
+ for (typeCast = type_casts; typeCast->canCast != NULL; typeCast++) {
246
+ if (typeCast->canCast(type_klass)) {
247
+ typeCastedValue = typeCast->typeCast(value);
248
+ break;
249
+ }
250
+ }
251
+
252
+ if (typeCastedValue == Qundef) {
253
+ return rb_funcall(type_metadata, type_cast_from_database_id, 1, value);
254
+ }
255
+
256
+ return typeCastedValue;
257
+ }
258
+
259
+ VALUE public_type_cast(VALUE module, VALUE type_metadata, VALUE value) {
260
+ return type_cast(type_metadata, value);
261
+ }
262
+
263
+ void panko_init_type_cast(VALUE mPanko) {
264
+ type_cast_from_database_id = rb_intern_const("type_cast_from_database");
265
+ to_s_id = rb_intern_const("to_s");
266
+ to_i_id = rb_intern_const("to_i");
267
+
268
+ rb_define_singleton_method(mPanko, "_type_cast", public_type_cast, 2);
269
+
270
+ panko_init_time(mPanko);
271
+ }
@@ -0,0 +1,74 @@
1
+ #include <ruby.h>
2
+ #include <stdbool.h>
3
+
4
+ /*
5
+ * Type Casting
6
+ *
7
+ * We do "special" type casting which is mix of two inspirations:
8
+ * *) light records gem
9
+ * *) pg TextDecoders
10
+ *
11
+ * The whole idea behind those type casts, are to do the minimum required
12
+ * type casting in the most performant manner and *allocation free*.
13
+ *
14
+ * For example, in `ActiveRecord::Type::String` the type_cast_from_database
15
+ * creates new string, for known reasons, but, in serialization flow we don't
16
+ * need to create new string becuase we afraid of mutations.
17
+ *
18
+ * Since we know before hand, that we are only reading from the database, and
19
+ * *not* writing and the end result if for JSON we can skip some "defenses".
20
+ */
21
+
22
+ typedef bool (*TypeMatchFunc)(VALUE type_klass);
23
+
24
+ /*
25
+ * TypeCastFunc
26
+ *
27
+ * @return VALUE casted value or Qundef if not casted
28
+ */
29
+ typedef VALUE (*TypeCastFunc)(VALUE value);
30
+
31
+ typedef struct _TypeCast {
32
+ TypeMatchFunc canCast;
33
+ TypeCastFunc typeCast;
34
+ } * TypeCast;
35
+
36
+ // ActiveRecord::Type::String
37
+ // ActiveRecord::Type::Text
38
+ bool is_string_or_text_type(VALUE type_klass);
39
+ VALUE cast_string_or_text_type(VALUE value);
40
+
41
+ // ActiveRecord::Type::Float
42
+ bool is_float_type(VALUE type_klass);
43
+ VALUE cast_float_type(VALUE value);
44
+
45
+ // ActiveRecord::Type::Integer
46
+ bool is_integer_type(VALUE type_klass);
47
+ VALUE cast_integer_type(VALUE value);
48
+
49
+ // ActiveRecord::ConnectoinAdapters::PostgreSQL::Json
50
+ bool is_json_type(VALUE type_klass);
51
+ VALUE cast_json_type(VALUE value);
52
+
53
+ // ActiveRecord::Type::Boolean
54
+ bool is_boolean_type(VALUE type_klass);
55
+ VALUE cast_boolean_type(VALUE value);
56
+
57
+ // ActiveRecord::Type::DateTime
58
+ // ActiveRecord::ConnectionAdapters::PostgreSQL::OID::DateTime
59
+ // ActiveRecord::AttributeMethods::TimeZoneConversion::TimeZoneConverter
60
+ bool is_date_time_type(VALUE type_klass);
61
+ VALUE cast_date_time_type(VALUE value);
62
+
63
+ static struct _TypeCast type_casts[] = {
64
+ {is_string_or_text_type, cast_string_or_text_type},
65
+ {is_integer_type, cast_integer_type},
66
+ {is_boolean_type, cast_boolean_type},
67
+ {is_date_time_type, cast_date_time_type},
68
+ {is_float_type, cast_float_type},
69
+ {is_json_type, cast_json_type},
70
+
71
+ {NULL, NULL}};
72
+
73
+ extern VALUE type_cast(VALUE type_metadata, VALUE value);
74
+ void panko_init_type_cast(VALUE mPanko);
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+ require_relative "cache"
3
+
4
+ module Panko
5
+ class ArraySerializer
6
+ attr_accessor :subjects
7
+
8
+ def initialize(subjects, options = {})
9
+ @subjects = subjects
10
+ @each_serializer = options[:each_serializer]
11
+
12
+
13
+ serializer_options = {
14
+ only: options.fetch(:only, []),
15
+ except: options.fetch(:except, [])
16
+ }
17
+
18
+ @descriptor = Panko::CACHE.fetch(@each_serializer, serializer_options)
19
+ @context = options.fetch(:context, nil)
20
+ end
21
+
22
+ def to_json
23
+ serialize_to_json @subjects
24
+ end
25
+
26
+ def serialize(subjects)
27
+ Oj.load(serialize_to_json(subjects))
28
+ end
29
+
30
+ def to_a
31
+ Oj.load(serialize_to_json(@subjects))
32
+ end
33
+
34
+ def serialize_to_json(subjects)
35
+ writer = Oj::StringWriter.new(mode: :rails)
36
+ Panko::serialize_subjects(subjects.to_a, writer, @descriptor, @context)
37
+ writer.to_s
38
+ end
39
+ end
40
+ end