From 6aeba94473199c3cc0698a10e12265f9d7c01647 Mon Sep 17 00:00:00 2001
From: Tim Bunce <Tim.Bunce@pobox.com>
Date: Mon, 28 Mar 2016 13:48:16 +0100
Subject: [PATCH] Support unquoted keys (the valid but non-canonical hstore
format)
---
hstore.xs | 34 ++++++++++++++++++++++++++--------
t/decode-more.t | 22 +++++++++++++++++-----
2 files changed, 43 insertions(+), 13 deletions(-)
diff --git a/hstore.xs b/hstore.xs
index 5314e1d..f6057da 100644
--- a/hstore.xs
+++ b/hstore.xs
@@ -10,29 +10,47 @@
#define ENCODE_BUF 128
#define SKIPSPACES(p) while( *p==' ' || *p=='\t' || *p=='\r' || *p=='\n' ) p++;
#define SKIPSPACES_P(p) while( (*p)[0]==' ' || (*p)[0]=='\t' || (*p)[0]=='\r' || (*p)[0]=='\n' ) (*p)++;
+#define IS_DELIM_CHAR(c) (isspace(c) || (c==',') || (c=='=') || (c=='>'))
-int get_next_string(char **p, char *part) {
+int get_next_string(char **p, char *part, int is_key) {
SKIPSPACES_P(p);
- if( (*p)[0] == '"' ) {
+ if( (*p)[0] == '"' ) { // Work with quoted string
int cnt=0;
- // Work with sting
(*p)++;
while( (*p)[0] != '"' && (*p)[0] != 0 ) {
+ if ((*p)[0] == 0)
+ return -2; // Unexpected end of string
if( (*p)[0] == '\\' )
(*p)++;
*(part++) = *((*p)++);
cnt++;
}
*part = 0;
- if( (*p)[0] == '"' )
- (*p)++; //skip "
+ (*p)++; //skip "
+ // fprintf(stderr, "get_next_string returning %d: %s (quoted)\n", cnt, &part[-cnt]);
return cnt;
}
- else if( toupper((*p)[0]) == 'N' && toupper((*p)[1]) == 'U' && toupper((*p)[2]) == 'L' && toupper((*p)[3]) == 'L' ) {
+ else if( !is_key && toupper((*p)[0]) == 'N' && toupper((*p)[1]) == 'U' && toupper((*p)[2]) == 'L' && toupper((*p)[3]) == 'L' ) {
*part=0;
(*p)+=4;
+ // fprintf(stderr,"get_next_string returning %d: NULL\n", -1);
return -1;
}
+ else if ((*p)[0] != 0 && !IS_DELIM_CHAR((*p)[0])) { // work with unquoted string
+ int cnt=0;
+ while( !IS_DELIM_CHAR((*p)[0]) ) {
+ if ((*p)[0] == 0)
+ return -2; // Unexpected end of string
+ if( (*p)[0] == '\\' )
+ (*p)++;
+ *(part++) = *((*p)++);
+ cnt++;
+ }
+ *part = 0;
+ // fprintf(stderr,"get_next_string returning %d: %s (unquoted)\n", cnt, &part[-cnt]);
+ return cnt;
+ }
+ // fprintf(stderr,"get_next_string returning -2: ERROR\n");
return -2; //error
}
@@ -181,7 +199,7 @@ CODE:
// Iterate whole string
while( *p != 0 ) {
- r_key = get_next_string(&p, key);
+ r_key = get_next_string(&p, key, 1);
if( r_key < 0 ) //lets think keys cannot be NULL
break; //Error
@@ -191,7 +209,7 @@ CODE:
break;
p+=2;
- r_val = get_next_string(&p, value);
+ r_val = get_next_string(&p, value, 0);
if( r_val == -2 )
break; //Error
diff --git a/t/decode-more.t b/t/decode-more.t
index 56081b8..89e544f 100644
--- a/t/decode-more.t
+++ b/t/decode-more.t
@@ -7,7 +7,7 @@ use strict;
use warnings;
use Data::Dumper;
-use Test::More tests=>25;
+use Test::Most;
BEGIN { use_ok('Pg::hstore') };
#########################
@@ -19,14 +19,14 @@ while( my $row = <DATA> ) {
$row =~ s/\=\>\s*NULL/\=\>undef/gs;
$row =~ s/([\@\$\%])/\\$1/gs;
my $t2 = eval "{$row}";
- is_deeply($t1, $t2, "Test string $srcrow");
+ eq_or_diff($t1, $t2, "Test string $srcrow");
}
my @addtests=(
"\"win1251\" => \"\xc4\xee\xea\xee\xeb\xe5\x3f\"",
"\"binary\" => \"\x07\x03\xdb\xdb\x7a\xa7\xda\xad\x49\x94\xa0\x0a\"",
"\"tab\" =>\"\t\"",
- "\"\t\"=>\"tab\""
+ "\"\t\"=>\"tab\"",
);
foreach my $row (@addtests) {
my $t1 = Pg::hstore::decode($row);
@@ -35,9 +35,21 @@ foreach my $row (@addtests) {
$row =~ s/\=\>\s*NULL/\=\>undef/gs;
$row =~ s/([\@\$\%])/\\$1/gs;
my $t2 = eval "{$row}";
- is_deeply($t1, $t2, "Test string $srcrow");
+ eq_or_diff($t1, $t2, "Test string $srcrow");
}
+my @others = (
+ [ q{"IsNull" => NULL}, { "IsNull" => undef } ],
+ [ q{unquoted => "WithQuotedKey"}, { "unquoted" => "WithQuotedKey" } ],
+ [ q{NULL => NULL}, { "NULL" => undef } ],
+);
+foreach my $row (@others) {
+ my ($hstore_string, $expected_data) = @$row;
+ eq_or_diff(Pg::hstore::decode($hstore_string), $expected_data, "Test string $hstore_string");
+}
+
+
+done_testing();
__DATA__
"test"=>"1"
@@ -59,4 +71,4 @@ __DATA__
"all"=> "2gether", "test"=>"1" , "empty"=> "","slash"=>"\\","doubleslash"=>"\\\\","quote"=>"\"" ,"a" => "1" , "b"=>"2", "c" =>NULL,"spec" => "~!@#$%^&*()_+|-=\\/';\",.[]{}:<>?`","russian" => "Ðоколе?","1"=>"test", "\\"=>"slash" , "\\\\"=>"doubleslash" ,"\""=>"quote", "~!@#$%^&*()_+|-=\\/';\",.[]{}:<>?`"=>"spec","Ðоколе?"=>"russian",
"site_url"=>"
http://test.domain.ru", "max_downloads"=>"-1", "support_email"=>"support@test.ru"
-"price"=>"35.88", "shortnum"=>"770111", "period_id"=>"1", "price_desc"=>"11111 Ñ/нед", "prolong_type"=>"402", "sale_gateway_id"=>"1013", "infosms_src_addr"=>"770111", "shortnum_prolong"=>"770111", "price_currency_id"=>"1", "shortnum_start_is_prepaid"=>"0"
\ No newline at end of file
+"price"=>"35.88", "shortnum"=>"770111", "period_id"=>"1", "price_desc"=>"11111 Ñ/нед", "prolong_type"=>"402", "sale_gateway_id"=>"1013", "infosms_src_addr"=>"770111", "shortnum_prolong"=>"770111", "price_currency_id"=>"1", "shortnum_start_is_prepaid"=>"0"
--
2.5.4 (Apple Git-61)