tilia-dav 3.1.0.pre.alpha6 → 3.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/CHANGELOG.sabre.md +97 -1
- data/Gemfile +1 -16
- data/Gemfile.lock +44 -41
- data/LICENSE +1 -1
- data/LICENSE.sabre +1 -1
- data/examples/addressbookserver.rb +52 -0
- data/examples/calendarserver.rb +58 -0
- data/examples/fileserver.rb +57 -0
- data/examples/groupwareserver.rb +69 -0
- data/examples/sql/mysql.addressbook.sql +28 -0
- data/examples/sql/mysql.calendars.sql +64 -0
- data/examples/sql/mysql.locks.sql +13 -0
- data/examples/sql/mysql.principals.sql +21 -0
- data/examples/sql/mysql.propertystorage.sql +8 -0
- data/examples/sql/mysql.users.sql +9 -0
- data/examples/sql/pgsql.addressbook.sql +52 -0
- data/examples/sql/pgsql.calendars.sql +93 -0
- data/examples/sql/pgsql.locks.sql +19 -0
- data/examples/sql/pgsql.principals.sql +38 -0
- data/examples/sql/pgsql.propertystorage.sql +13 -0
- data/examples/sql/pgsql.users.sql +14 -0
- data/examples/sql/sqlite.addressbooks.sql +28 -0
- data/examples/sql/sqlite.calendars.sql +64 -0
- data/examples/sql/sqlite.locks.sql +12 -0
- data/examples/sql/sqlite.principals.sql +20 -0
- data/examples/sql/sqlite.propertystorage.sql +10 -0
- data/examples/sql/sqlite.users.sql +9 -0
- data/lib/tilia/cal_dav/ics_export_plugin.rb +1 -1
- data/lib/tilia/cal_dav/plugin.rb +27 -11
- data/lib/tilia/cal_dav/schedule/i_mip_plugin.rb +2 -2
- data/lib/tilia/cal_dav/schedule/plugin.rb +7 -0
- data/lib/tilia/dav/auth/backend.rb +1 -0
- data/lib/tilia/dav/auth/backend/abstract_basic.rb +3 -2
- data/lib/tilia/dav/auth/backend/abstract_bearer.rb +116 -0
- data/lib/tilia/dav/auth/backend/abstract_digest.rb +3 -2
- data/lib/tilia/dav/auth/backend/apache.rb +2 -1
- data/lib/tilia/dav/auth/backend/sequel.rb +2 -9
- data/lib/tilia/dav/client.rb +29 -3
- data/lib/tilia/dav/core_plugin.rb +1 -2
- data/lib/tilia/dav/server.rb +16 -4
- data/lib/tilia/dav/temporary_file_filter_plugin.rb +3 -0
- data/lib/tilia/dav/tree.rb +4 -3
- data/lib/tilia/dav/version.rb +1 -1
- data/lib/tilia/dav/xml/element/response.rb +20 -2
- data/lib/tilia/dav_acl/principal_backend/sequel.rb +50 -6
- data/test/cal_dav/ics_export_plugin_test.rb +1 -0
- data/test/cal_dav/plugin_test.rb +4 -4
- data/test/cal_dav/schedule/plugin_properties_test.rb +51 -0
- data/test/card_dav/backend/sequel_my_sql_test.rb +3 -3
- data/test/card_dav/vcf_export_test.rb +11 -1
- data/test/dav/auth/backend/abstract_bearer_test.rb +71 -0
- data/test/dav/client_test.rb +42 -4
- data/test/dav/core_plugin_test.rb +12 -0
- data/test/dav/fs_ext/server_test.rb +1 -1
- data/test/dav/http_copy_test.rb +185 -0
- data/test/dav/mock/collection.rb +6 -9
- data/test/dav/mock/file.rb +10 -9
- data/test/dav/mock/streaming_file.rb +1 -3
- data/test/dav/server_events_test.rb +8 -6
- data/test/dav/server_range_test.rb +135 -155
- data/test/dav/server_simple_test.rb +14 -0
- data/test/dav/xml/element/response_test.rb +56 -1
- data/test/dav/xml/property/href_test.rb +14 -0
- data/test/dav_acl/principal_backend/abstract_sequel_test.rb +16 -0
- data/test/dav_acl/principal_backend/mock.rb +1 -1
- data/test/dav_server_test.rb +1 -1
- data/tilia-dav.gemspec +2 -2
- metadata +38 -14
- data/test/dav/copy_test.rb +0 -33
- data/test/dav/server_copy_move_test.rb +0 -164
@@ -4,15 +4,6 @@ module Tilia
|
|
4
4
|
module Backend
|
5
5
|
# This is an authentication backend that uses a database to manage passwords.
|
6
6
|
class Sequel < AbstractDigest
|
7
|
-
protected
|
8
|
-
|
9
|
-
# Reference to PDO connection
|
10
|
-
#
|
11
|
-
# @var PDO
|
12
|
-
attr_accessor :pdo
|
13
|
-
|
14
|
-
public
|
15
|
-
|
16
7
|
# PDO table name we'll be using
|
17
8
|
#
|
18
9
|
# @var string
|
@@ -26,6 +17,8 @@ module Tilia
|
|
26
17
|
def initialize(sequel)
|
27
18
|
@sequel = sequel
|
28
19
|
@table_name = 'users'
|
20
|
+
|
21
|
+
super()
|
29
22
|
end
|
30
23
|
|
31
24
|
# Returns the digest hash for a user.
|
data/lib/tilia/dav/client.rb
CHANGED
@@ -118,6 +118,8 @@ module Tilia
|
|
118
118
|
add_curl_setting(:encoding, encodings.join(','))
|
119
119
|
end
|
120
120
|
|
121
|
+
add_curl_setting(:useragent, "tilia-dav/#{Version::VERSION} (http://sabre.io/)")
|
122
|
+
|
121
123
|
@xml = Xml::Service.new
|
122
124
|
# BC
|
123
125
|
@property_map = @xml.element_map
|
@@ -181,7 +183,7 @@ module Tilia
|
|
181
183
|
|
182
184
|
response = send_request(request)
|
183
185
|
|
184
|
-
fail
|
186
|
+
fail Http::ClientHttpException.new(response) if response.status.to_i >= 400
|
185
187
|
|
186
188
|
result = parse_multi_status(response.body_as_string)
|
187
189
|
|
@@ -207,7 +209,7 @@ module Tilia
|
|
207
209
|
#
|
208
210
|
# @param string url
|
209
211
|
# @param array properties
|
210
|
-
# @return
|
212
|
+
# @return bool
|
211
213
|
def prop_patch(url, properties)
|
212
214
|
prop_patch = Xml::Request::PropPatch.new
|
213
215
|
prop_patch.properties = properties
|
@@ -220,7 +222,31 @@ module Tilia
|
|
220
222
|
{ 'Content-Type' => 'application/xml' },
|
221
223
|
xml
|
222
224
|
)
|
223
|
-
|
225
|
+
|
226
|
+
response = send_request(request)
|
227
|
+
|
228
|
+
fail Http::ClientHttpException.new(response) if response.status.to_i >= 400
|
229
|
+
|
230
|
+
if response.status == 207.to_i
|
231
|
+
# If it's a 207, the request could still have failed, but the
|
232
|
+
# information is hidden in the response body.
|
233
|
+
result = parse_multi_status(response.body_as_string)
|
234
|
+
|
235
|
+
error_properties = []
|
236
|
+
result.each do |href, status_list|
|
237
|
+
status_list.each do |status, properties|
|
238
|
+
next unless status.to_i >= 400
|
239
|
+
|
240
|
+
properties.each do |prop_name, prop_value|
|
241
|
+
error_properties << "#{prop_name} (#{status})"
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
fail Http::ClientException, "ROPPATCH failed. The following properties errored: #{error_properties.join(', ')}" if error_properties.any?
|
247
|
+
end
|
248
|
+
|
249
|
+
true
|
224
250
|
end
|
225
251
|
|
226
252
|
# Performs an HTTP options request
|
@@ -589,13 +589,12 @@ module Tilia
|
|
589
589
|
|
590
590
|
copy_info = @server.copy_and_move_info(request)
|
591
591
|
|
592
|
+
return false unless @server.emit('beforeBind', [copy_info['destination']])
|
592
593
|
if copy_info['destinationExists']
|
593
594
|
return false unless @server.emit('beforeUnbind', [copy_info['destination']])
|
594
595
|
@server.tree.delete(copy_info['destination'])
|
595
596
|
end
|
596
597
|
|
597
|
-
return false unless @server.emit('beforeBind', [copy_info['destination']])
|
598
|
-
|
599
598
|
@server.tree.copy(path, copy_info['destination'])
|
600
599
|
|
601
600
|
@server.emit('afterBind', [copy_info['destination']])
|
data/lib/tilia/dav/server.rb
CHANGED
@@ -327,11 +327,18 @@ module Tilia
|
|
327
327
|
|
328
328
|
if emit("method:#{method}", [request, response])
|
329
329
|
if emit('method', [request, response])
|
330
|
-
|
331
|
-
|
330
|
+
ex_message = "There was no plugin in the system that was willing to handle this #{method} method."
|
331
|
+
if method == "GET"
|
332
|
+
ex_message += " Enable the Browser plugin to get a better result here."
|
333
|
+
end
|
334
|
+
|
335
|
+
# Unsupported method
|
336
|
+
fail Exception::NotImplemented, ex_message
|
332
337
|
end
|
333
338
|
end
|
334
339
|
|
340
|
+
fail Exception, 'No subsystem set a valid HTTP status code. Something must have interrupted the request without providing further detail.' unless response.status
|
341
|
+
|
335
342
|
return nil unless emit("afterMethod:#{method}", [request, response])
|
336
343
|
return nil unless emit('afterMethod', [request, response])
|
337
344
|
|
@@ -381,7 +388,12 @@ module Tilia
|
|
381
388
|
calculate_uri(@http_request.url)
|
382
389
|
end
|
383
390
|
|
384
|
-
#
|
391
|
+
# Turns a URI such as the REQUEST_URI into a local path.
|
392
|
+
#
|
393
|
+
# This method:
|
394
|
+
# * strips off the base path
|
395
|
+
# * normalizes the path
|
396
|
+
# * uri-decodes the path
|
385
397
|
#
|
386
398
|
# @param string uri
|
387
399
|
# @throws Exception\Forbidden A permission denied exception is thrown whenever there was an attempt to supply a uri outside of the base uri
|
@@ -677,7 +689,7 @@ module Tilia
|
|
677
689
|
sub_prop_find = prop_find.clone
|
678
690
|
sub_prop_find.depth = new_depth
|
679
691
|
|
680
|
-
if path
|
692
|
+
if path.present?
|
681
693
|
sub_path = path + '/' + child_node.name
|
682
694
|
else
|
683
695
|
sub_path = child_node.name
|
@@ -107,6 +107,9 @@ module Tilia
|
|
107
107
|
#
|
108
108
|
# @param string uri
|
109
109
|
# @param resource data
|
110
|
+
# @param DAV\ICollection _parent_node
|
111
|
+
# @param bool _modified Should be set to true, if this event handler
|
112
|
+
# changed &data.
|
110
113
|
# @return bool
|
111
114
|
def before_create_file(uri, data, _parent, _modified)
|
112
115
|
temp_path = temp_file?(uri)
|
data/lib/tilia/dav/tree.rb
CHANGED
@@ -49,7 +49,7 @@ module Tilia
|
|
49
49
|
# Otherwise, we recursively grab the parent and ask him/her.
|
50
50
|
parent = node_for_path(parent_name)
|
51
51
|
|
52
|
-
unless parent.is_a?
|
52
|
+
unless parent.is_a?(ICollection)
|
53
53
|
fail Exception::NotFound, "Could not find node at path: #{path}"
|
54
54
|
end
|
55
55
|
|
@@ -145,10 +145,11 @@ module Tilia
|
|
145
145
|
def children(path)
|
146
146
|
node = node_for_path(path)
|
147
147
|
children = node.children
|
148
|
-
|
148
|
+
base_path = path.gsub(/^\/+|\/+$/, '')
|
149
|
+
base_path += '/' unless base_path.blank?
|
149
150
|
|
150
151
|
children.each do |child|
|
151
|
-
cache[
|
152
|
+
cache[base_path + child.name] = child
|
152
153
|
end
|
153
154
|
|
154
155
|
children
|
data/lib/tilia/dav/version.rb
CHANGED
@@ -81,7 +81,9 @@ module Tilia
|
|
81
81
|
if @http_status
|
82
82
|
writer.write_element('{DAV:}status', "HTTP/1.1 #{@http_status} #{Tilia::Http::Response.status_codes[@http_status]}")
|
83
83
|
end
|
84
|
-
writer.write_element('{DAV:}href', writer.context_uri + href)
|
84
|
+
writer.write_element('{DAV:}href', writer.context_uri + Http::encode_path(href))
|
85
|
+
|
86
|
+
empty = true
|
85
87
|
|
86
88
|
@response_properties.each do |status, properties|
|
87
89
|
# Skipping empty lists
|
@@ -90,11 +92,27 @@ module Tilia
|
|
90
92
|
next if properties.blank?
|
91
93
|
next if status.to_i.to_s != status.to_s
|
92
94
|
|
95
|
+
empty = false
|
96
|
+
|
93
97
|
writer.start_element('{DAV:}propstat')
|
94
98
|
writer.write_element('{DAV:}prop', properties)
|
95
99
|
writer.write_element('{DAV:}status', "HTTP/1.1 #{status} #{Tilia::Http::Response.status_codes[status]}")
|
96
100
|
writer.end_element # {DAV:}propstat
|
97
101
|
end
|
102
|
+
|
103
|
+
if empty
|
104
|
+
# The WebDAV spec _requires_ at least one DAV:propstat to appear for
|
105
|
+
# every DAV:response. In some circumstances however, there are no
|
106
|
+
# properties to encode.
|
107
|
+
#
|
108
|
+
# In those cases we MUST specify at least one DAV:propstat anyway, with
|
109
|
+
# no properties.
|
110
|
+
writer.write_element(
|
111
|
+
'{DAV:}propstat',
|
112
|
+
'{DAV:}prop' => [],
|
113
|
+
'{DAV:}status' => "HTTP/1.1 418 #{Http::Response.status_codes[418]}"
|
114
|
+
)
|
115
|
+
end
|
98
116
|
end
|
99
117
|
|
100
118
|
# The deserialize method is called during xml parsing.
|
@@ -173,7 +191,7 @@ module Tilia
|
|
173
191
|
status = elem['value']['{DAV:}status']
|
174
192
|
status = status.split(' ')[1]
|
175
193
|
properties = elem['value'].key?('{DAV:}prop') ? elem['value']['{DAV:}prop'] : []
|
176
|
-
property_lists[status] = properties
|
194
|
+
property_lists[status] = properties if properties.any?
|
177
195
|
when '{DAV:}status'
|
178
196
|
status_code = elem['value'].split(' ')[1]
|
179
197
|
end
|
@@ -198,22 +198,26 @@ module Tilia
|
|
198
198
|
# @param array search_properties
|
199
199
|
# @param string test
|
200
200
|
# @return array
|
201
|
-
def search_principals(prefix_path, search_properties,
|
202
|
-
|
201
|
+
def search_principals(prefix_path, search_properties, test = 'allof')
|
202
|
+
return [] if search_properties.empty? # No criteria
|
203
|
+
|
204
|
+
query = "SELECT uri FROM #{@table_name} WHERE "
|
203
205
|
values = []
|
204
206
|
|
205
207
|
search_properties.each do |property, value|
|
206
208
|
case property
|
207
209
|
when '{DAV:}displayname'
|
208
|
-
|
209
|
-
values << "%#{value}%"
|
210
|
+
column = 'displayname'
|
210
211
|
when '{http://sabredav.org/ns}email-address'
|
211
|
-
|
212
|
-
values << "%#{value}%"
|
212
|
+
column = 'email'
|
213
213
|
else
|
214
214
|
# Unsupported property
|
215
215
|
return []
|
216
216
|
end
|
217
|
+
|
218
|
+
query += test == 'anyof' ? ' OR ' : ' AND ' if values.any?
|
219
|
+
query += "lower(#{column}) LIKE lower(?)"
|
220
|
+
values << "%#{value}%"
|
217
221
|
end
|
218
222
|
|
219
223
|
principals = []
|
@@ -228,6 +232,46 @@ module Tilia
|
|
228
232
|
principals
|
229
233
|
end
|
230
234
|
|
235
|
+
# Finds a principal by its URI.
|
236
|
+
#
|
237
|
+
# This method may receive any type of uri, but mailto: addresses will be
|
238
|
+
# the most common.
|
239
|
+
#
|
240
|
+
# Implementation of this API is optional. It is currently used by the
|
241
|
+
# CalDAV system to find principals based on their email addresses. If this
|
242
|
+
# API is not implemented, some features may not work correctly.
|
243
|
+
#
|
244
|
+
# This method must return a relative principal path, or null, if the
|
245
|
+
# principal was not found or you refuse to find it.
|
246
|
+
#
|
247
|
+
# @param string uri
|
248
|
+
# @param string principal_prefix
|
249
|
+
# @return string, nil
|
250
|
+
def find_by_uri(uri, principal_prefix)
|
251
|
+
value = nil
|
252
|
+
scheme = nil
|
253
|
+
(scheme, value) = uri.split(":", 2)
|
254
|
+
return nil unless value
|
255
|
+
|
256
|
+
uri = nil
|
257
|
+
case scheme
|
258
|
+
when "mailto"
|
259
|
+
@sequel.fetch("SELECT uri FROM #{@table_name} WHERE lower(email)=lower(?)", [value]) do |row|
|
260
|
+
# Checking if the principal is in the prefix
|
261
|
+
row_prefix = Http::UrlUtil.split_path(row[:uri]).first
|
262
|
+
next unless row_prefix == principal_prefix
|
263
|
+
|
264
|
+
uri = row[:uri]
|
265
|
+
break # Stop on first match
|
266
|
+
end
|
267
|
+
else
|
268
|
+
#unsupported uri scheme
|
269
|
+
return nil
|
270
|
+
end
|
271
|
+
|
272
|
+
uri
|
273
|
+
end
|
274
|
+
|
231
275
|
# Returns the list of members for a group-principal
|
232
276
|
#
|
233
277
|
# @param string principal
|
data/test/cal_dav/plugin_test.rb
CHANGED
@@ -593,7 +593,7 @@ XML
|
|
593
593
|
utc = ActiveSupport::TimeZone.new('UTC')
|
594
594
|
expected_ical = DatabaseUtil.get_test_calendar_data
|
595
595
|
expected_ical = VObject::Reader.read(expected_ical)
|
596
|
-
expected_ical.expand(
|
596
|
+
expected_ical = expected_ical.expand(
|
597
597
|
utc.parse('2011-01-01 00:00:00'),
|
598
598
|
utc.parse('2011-12-31 23:59:59')
|
599
599
|
)
|
@@ -651,7 +651,7 @@ XML
|
|
651
651
|
utc = ActiveSupport::TimeZone.new('UTC')
|
652
652
|
expected_ical = DatabaseUtil.get_test_calendar_data
|
653
653
|
expected_ical = VObject::Reader.read(expected_ical)
|
654
|
-
expected_ical.expand(
|
654
|
+
expected_ical = expected_ical.expand(
|
655
655
|
utc.parse('2000-01-01 00:00:00'),
|
656
656
|
utc.parse('2010-12-31 23:59:59')
|
657
657
|
)
|
@@ -711,7 +711,7 @@ XML
|
|
711
711
|
utc = ActiveSupport::TimeZone.new('UTC')
|
712
712
|
expected_ical = DatabaseUtil.get_test_calendar_data
|
713
713
|
expected_ical = VObject::Reader.read(expected_ical)
|
714
|
-
expected_ical.expand(
|
714
|
+
expected_ical = expected_ical.expand(
|
715
715
|
utc.parse('2000-01-01 00:00:00'),
|
716
716
|
utc.parse('2010-12-31 23:59:59')
|
717
717
|
)
|
@@ -865,7 +865,7 @@ XML
|
|
865
865
|
utc = ActiveSupport::TimeZone.new('UTC')
|
866
866
|
expected_ical = DatabaseUtil.get_test_calendar_data
|
867
867
|
expected_ical = VObject::Reader.read(expected_ical)
|
868
|
-
expected_ical.expand(
|
868
|
+
expected_ical = expected_ical.expand(
|
869
869
|
utc.parse('2000-01-01 00:00:00'),
|
870
870
|
utc.parse('2010-12-31 23:59:59')
|
871
871
|
)
|
@@ -16,6 +16,9 @@ module Tilia
|
|
16
16
|
'default',
|
17
17
|
{}
|
18
18
|
)
|
19
|
+
@principal_backend.add_principal(
|
20
|
+
'uri' => 'principals/user1/calendar-proxy-read'
|
21
|
+
)
|
19
22
|
end
|
20
23
|
|
21
24
|
def test_principal_properties
|
@@ -57,6 +60,54 @@ module Tilia
|
|
57
60
|
assert_equal('calendars/user1/default/', prop.href)
|
58
61
|
end
|
59
62
|
|
63
|
+
def test_principal_properties_bad_principal
|
64
|
+
props = @server.properties_for_path(
|
65
|
+
'principals/user1/calendar-proxy-read',
|
66
|
+
[
|
67
|
+
'{urn:ietf:params:xml:ns:caldav}schedule-inbox-URL',
|
68
|
+
'{urn:ietf:params:xml:ns:caldav}schedule-outbox-URL',
|
69
|
+
'{urn:ietf:params:xml:ns:caldav}calendar-user-address-set',
|
70
|
+
'{urn:ietf:params:xml:ns:caldav}calendar-user-type',
|
71
|
+
'{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL',
|
72
|
+
]
|
73
|
+
)
|
74
|
+
|
75
|
+
assert(props[0])
|
76
|
+
assert_has_key(200, props[0])
|
77
|
+
assert_has_key(404, props[0])
|
78
|
+
|
79
|
+
assert_has_key('{urn:ietf:params:xml:ns:caldav}schedule-outbox-URL', props[0][404])
|
80
|
+
assert_has_key('{urn:ietf:params:xml:ns:caldav}schedule-inbox-URL', props[0][404])
|
81
|
+
|
82
|
+
prop = props[0][200]['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set']
|
83
|
+
assert(prop.is_a?(Dav::Xml::Property::Href))
|
84
|
+
assert_equal(['/principals/user1/calendar-proxy-read/'], prop.hrefs)
|
85
|
+
|
86
|
+
assert_has_key('{urn:ietf:params:xml:ns:caldav}calendar-user-type', props[0][200])
|
87
|
+
prop = props[0][200]['{urn:ietf:params:xml:ns:caldav}calendar-user-type']
|
88
|
+
assert_equal('INDIVIDUAL', prop)
|
89
|
+
|
90
|
+
assert_has_key('{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL', props[0][404])
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_no_default_calendar
|
94
|
+
@caldav_backend.calendars_for_user('principals/user1').each do |calendar|
|
95
|
+
@caldav_backend.delete_calendar(calendar['id'])
|
96
|
+
end
|
97
|
+
|
98
|
+
props = @server.properties_for_path(
|
99
|
+
'/principals/user1',
|
100
|
+
[
|
101
|
+
'{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL'
|
102
|
+
]
|
103
|
+
)
|
104
|
+
|
105
|
+
assert(props[0])
|
106
|
+
assert_has_key(404, props[0])
|
107
|
+
|
108
|
+
assert_has_key('{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL', props[0][404])
|
109
|
+
end
|
110
|
+
|
60
111
|
# There are two properties for availability. The server should
|
61
112
|
# automatically map the old property to the standard property.
|
62
113
|
def test_availability_mapping
|
@@ -21,7 +21,7 @@ CREATE TABLE addressbooks (
|
|
21
21
|
description VARCHAR(10000),
|
22
22
|
synctoken INT(11) UNSIGNED NOT NULL DEFAULT '1',
|
23
23
|
UNIQUE(principaluri(100), uri(100))
|
24
|
-
) #{TestUtil.mysql_engine} DEFAULT CHARSET=
|
24
|
+
) #{TestUtil.mysql_engine} DEFAULT CHARSET=utf8mb4
|
25
25
|
QUERY
|
26
26
|
)
|
27
27
|
db.run(<<QUERY
|
@@ -33,7 +33,7 @@ CREATE TABLE cards (
|
|
33
33
|
lastmodified INT(11) UNSIGNED,
|
34
34
|
etag VARBINARY(32),
|
35
35
|
size INT(11) UNSIGNED NOT NULL
|
36
|
-
) #{TestUtil.mysql_engine} DEFAULT CHARSET=
|
36
|
+
) #{TestUtil.mysql_engine} DEFAULT CHARSET=utf8mb4
|
37
37
|
QUERY
|
38
38
|
)
|
39
39
|
db.run(<<QUERY
|
@@ -44,7 +44,7 @@ CREATE TABLE addressbookchanges (
|
|
44
44
|
addressbookid INT(11) UNSIGNED NOT NULL,
|
45
45
|
operation SMALLINT(2) NOT NULL,
|
46
46
|
INDEX addressbookid_synctoken (addressbookid, synctoken)
|
47
|
-
) #{TestUtil.mysql_engine} DEFAULT CHARSET=
|
47
|
+
) #{TestUtil.mysql_engine} DEFAULT CHARSET=utf8mb4
|
48
48
|
QUERY
|
49
49
|
)
|
50
50
|
|