Skip Menu |

This queue is for tickets about the Pg-hstore CPAN distribution.

Report information
The Basics
Id: 112671
Status: resolved
Priority: 0/
Queue: Pg-hstore

People
Owner: Nobody in particular
Requestors: TIMB [...] cpan.org
Cc:
AdminCc:

Bug Information
Severity: (no value)
Broken in: (no value)
Fixed in: (no value)



Subject: Doesn't support unquoted keys
The hstore format - http://www.postgresql.org/docs/current/static/hstore.html - only requires keys and values to be quoted if they "include whitespace, commas, =s or >s". The current code silently returns an empty hash when given a string where the keys aren't quoted.
I'll probably work on a patch for this soon.
I've attached a patch to Pg-hstore-1.05 that fixed this. Any chance you could make a release soonish? (I'd be open to taking co-maint on this in order to be able to make a release for you, if that would help.) Tim.
Subject: 0001-Support-unquoted-keys-the-valid-but-non-canonical-hs.patch
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)
Released 1.06
On Mon Mar 28 15:42:57 2016, ALT wrote: Show quoted text
> Released 1.06
Thanks!