panko_serializer 0.1.1

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.
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