panko_serializer 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|