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.
- checksums.yaml +7 -0
- data/.clang-format +102 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.rubocop.yml +22 -0
- data/.travis.yml +8 -0
- data/Gemfile +36 -0
- data/LICENSE.txt +21 -0
- data/README.md +8 -0
- data/Rakefile +62 -0
- data/benchmarks/BENCHMARKS.md +48 -0
- data/benchmarks/allocs.rb +23 -0
- data/benchmarks/app.rb +13 -0
- data/benchmarks/benchmarking_support.rb +43 -0
- data/benchmarks/bm_active_model_serializers.rb +45 -0
- data/benchmarks/bm_controller.rb +81 -0
- data/benchmarks/bm_panko_json.rb +60 -0
- data/benchmarks/bm_panko_object.rb +69 -0
- data/benchmarks/profile.rb +88 -0
- data/benchmarks/sanity.rb +67 -0
- data/benchmarks/setup.rb +62 -0
- data/benchmarks/type_casts/bm_active_record.rb +57 -0
- data/benchmarks/type_casts/bm_panko.rb +67 -0
- data/benchmarks/type_casts/bm_pg.rb +35 -0
- data/benchmarks/type_casts/support.rb +16 -0
- data/ext/panko_serializer/attributes_iterator.c +62 -0
- data/ext/panko_serializer/attributes_iterator.h +17 -0
- data/ext/panko_serializer/extconf.rb +8 -0
- data/ext/panko_serializer/panko_serializer.c +189 -0
- data/ext/panko_serializer/panko_serializer.h +17 -0
- data/ext/panko_serializer/serialization_descriptor.c +166 -0
- data/ext/panko_serializer/serialization_descriptor.h +30 -0
- data/ext/panko_serializer/time_conversion.c +94 -0
- data/ext/panko_serializer/time_conversion.h +6 -0
- data/ext/panko_serializer/type_cast.c +271 -0
- data/ext/panko_serializer/type_cast.h +74 -0
- data/lib/panko/array_serializer.rb +40 -0
- data/lib/panko/cache.rb +35 -0
- data/lib/panko/serialization_descriptor.rb +119 -0
- data/lib/panko/serializer.rb +57 -0
- data/lib/panko/version.rb +4 -0
- data/lib/panko.rb +8 -0
- data/panko_serializer.gemspec +31 -0
- 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,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
|