Fabricating hstore values for Postgresql - postgresql

As in title, I try to fabricate hash into hstore type column.
I have seen question fabricator with hstore attribute, but the solution there does not work for me.
My hstore column name is "status", there I want to set three flags: "processed", "duplicate", "eol". I'm using sequel (4.14.0) as ORM, fabrication (2.8.1), Ruby 2.1.2 and Postgresql of course ;)
case 1:
status {eol: true, duplicate: false, processed: true}
result:
syntax error
case 2:
status {"heol"=>"true", "hduplicate"=>"false", "hprocessed"=>"true"}
result:
syntax error
case 3:
status do
{"heol"=>"true", "hduplicate"=>"false", "hprocessed"=>"true"}
end
result:
Sequel::DatabaseError:
PG::DatatypeMismatch: ERROR: column "status" is of type hstore but expression is of type boolean
LINE 1: ...23.0, '2000-01-01', (('heol' = '...
HINT: You will need to rewrite or cast the expression.
case 4:
status do
{status: "heol:true"}
end
result:
Failure/Error: Fabricate(:entry)
Sequel::DatabaseError:
PG::UndefinedColumn: ERROR: column "status" does not exist
LINE 1: ...123.0, '2000-01-01', ("status" =...
HINT: There is a column named "status" in table "entries", but it cannot be referenced from this part of the query.
case 5:
status do
{'status' => "heol:true"} end
result:
Failure/Error: Fabricate(:entry)
Sequel::DatabaseError:
PG::DatatypeMismatch: ERROR: column "status" is of type hstore but expression is of type boolean
LINE 1: ...123.0, '2000-01-01', ('status' =...
HINT: You will need to rewrite or cast the expression.
case 6:
gave up ;)
result:
this question
With FactoryGirl everything works as expected, and syntax is straightforward:
FactoryGirl.define do
factory :entry do
status {{ flag_processed: true, flag_duplicate: false }}
end
Promise to make good use of the correct syntax in Fabrication =)
Thanks!
Lucas.

Case 1 and 2 are definitely not what you want. The Hash needs to be specified within a block, which is the same as what FactoryGirl is doing with your example containing double braces. Case 3, 4, and 5 would normally work but don't because Sequel has a special syntax for assigning hstore columns and Fabrication is not automatically translating it for you (because before you brought it up I had no idea this was a thing).
If you change it to this, I think you'll find success:
status do
Sequel.hstore("heol"=>"true", "hduplicate"=>"false", "hprocessed"=>"true")
end

Related

SQLAlchemy IN_ - trouble with leading zeroes

In my sqlalchemy ( sqlalchemy = "^1.4.36" ) query I have a clause:
.filter( some_model.some_field[2].in_(['item1', 'item2']) )
where some_field is jsonb and the value in some_field value in the db formatted like this:
["something","something","123"]
or
["something","something","0123"]
note: some_field[2] is always digits-only double-quoted string, sometimes with leading zeroes and sometimes without them.
The query works fine for cases like this:
.filter( some_model.some_field[2].in_(['123', '345']) )
and fails when the values in the in_ clause have leading zeroes:
e.g. .filter( some_model.some_field[2].in_(['0123', '0345']) ) fails.
The error it gives:
cursor.execute(statement, parameters)\\npsycopg2.errors.InvalidTextRepresentation: invalid input syntax for type json\\nLINE 3: ...d_on) = 2 AND (app_cache.value_metadata -> 2) IN (\\'0123\\'\\n ^\\nDETAIL: Token \"0123\" is invalid.
Again, in the case of '123' (or any string of digits without leading zero) instead of '0123' the error is not thrown.
What is wrong with having leading zeroes for the strings in the list of in_ clause? Thanks.
UPDATE: basically, sqlachemy's IN_ assumes int input and fails accordingly. There must be some reasoning behind this behavior, can't tell what it is. I removed that filter fromm the query and did the filtering of the ouput in python code afterwards.
The problem here is that the values in the IN clause are being interpreted by PostgreSQL as JSON representations of integers, and an integer with a leading zero is not valid JSON.
The IN clause has a value of type jsonb on the left hand side. The values on the right hand side are not explicitly typed, so Postgres tries to find the best match that will allow them to be compared with a jsonb value. This type is jsonb, so Postgres attempts to cast the values to jsonb. This works for values without a leading zero, because digits in single quotes without leading zeroes are valid representations of integers in JSON:
test# select '123'::jsonb;
jsonb
═══════
123
(1 row)
but it doesn't work for values with leading zeroes, because they are not valid JSON:
test# select '0123'::jsonb;
ERROR: invalid input syntax for type json
LINE 1: select '0123'::jsonb;
^
DETAIL: Token "0123" is invalid.
CONTEXT: JSON data, line 1: 0123
Assuming that you expect some_field[2].in_(['123', '345']) and some_field[2].in_(['0123', '345']) to match ["something","something","123"] and ["something","something","123"] respectively, you can either serialise the values to JSON yourself:
some_field[2].in_([json.dumps(x) for x in ['0123', '345']])
or use the contained_by operator (<# in PostgreSQL), to test whether some_field[2] is present in the list of values:
some_field[2].contained_by(['0123', '345'])
or cast some_field[2] to text (that is, use the ->> operator) so that the values are compared as text, not JSON.
some_field[2].astext.in_(['0123', '345'])

How to insert JSONB data from dependent table

I'm trying to insert data into a JSONB field based on a dependent table.
Essentially I want to do this (ignore why this is just an example query):
insert into myschema.teams (team_name, params)
select users.team_name, '{"team_name": teams.team_id, "user_name": users.username }'
from myschema.users
where users.team_name is not null;
As written I'm getting these errors:
ERROR: invalid input syntax for type json
LINE 2: ... '{"team_name...
^
DETAIL: Token "teams" is invalid.
CONTEXT: JSON data, line 1: {"team_name": teams...
You are using a string literal that doesn't contain valid JSON. There is no interpolation going on - you need use the jsonb_build_object function to create the JSONB value from dynamic values. (You could also do string concatenation and the cast from text to json, but please don't).
insert into myschema.teams (team_name, params)
select users.team_name, jsonb_build_object('team_name', teams.team_name, 'user_name', users.username)
from myschema.users
where users.team_name is not null;

Why is my UPDATE query with jsonb popping an error?

I'm trying to update a row in my PostgreSQL database and it's saying it's not finding the x column. the thing is the column pg is trying to find is actually a parameter for the new value in the jsonb_set function, so I'm at my wits end.
It's hard to explain, so I included the query and the error it throws.
Tried adding quotes, double-quotes, brackets, inside and out... didn't work.
UPDATE public.sometable
SET somecolumn = jsonb_set(somecolumn, '{firstKey, secondKey}', someInputString), update_date=NOW(), update_username="someone#somewhere.com"
WHERE id=1
RETURNING *
I'm expecting the value of the row I'm updating to be returned, instead I get:
ERROR: column "someInputString" does not exist
LINE 1: ...n = jsonb_set(somecolumn , '{firstKey, secondKey}', someInputString)...
You have to deliver a valid json value as the third argument of the function:
UPDATE public.sometable
SET
somecolumn = jsonb_set(somecolumn, '{firstKey, secondKey}', '"someInputString"'),
update_date = now(),
update_username = 'someone#somewhere.com'
WHERE id = 1
RETURNING *
Note, I guess update_username is a text, so you should use single quotes for a simple text.
Db<>fiddle.

No operator matches the given name and argument type(s): What needs to be cast?

I'm getting an error, and I cannot figure out why. I know the error is telling me to cast a type but I'm not sure on what?
What part of CASE is the operator?
ERROR: operator does not exist: character varying = boolean
LINE 6: WHEN lower(foo.name) SIMILAR TO '%(foo
^
HINT: No operator matches the given name and argument type(s). You might need to add explicit type casts.
My query:
SELECT foo.name,
bar.city,
MAX(foo.total - bar.tax_amount),
CASE bar.name
WHEN lower(foo.name) SIMILAR TO '%(foo|bar|baz)%' THEN true
ELSE false
END
....
GROUP BY foo.name, bar.city;
I believe you simply want:
SELECT foo.name,
bar.city,
MAX(foo.total - bar.tax_amount),
(CASE WHEN lower(foo.name) SIMILAR TO '%(foo|bar|baz)%' THEN true
ELSE false
END)
....
GROUP BY foo.name, bar.city;
The CASE expression has two forms. One searches for values for a given expression. This takes a column or expression right after the CASE. The second searches through various expressions, stopping at the first one. This takes the WHEN clause right after the CASE.
Or even more simply:
SELECT foo.name, bar.city,
MAX(foo.total - bar.tax_amount),
(lower(foo.name) SIMILAR TO '%(foo|bar|baz)%')
....
GROUP BY foo.name, bar.city;
The CASE is not needed.
Your CASE expression had a syntax error, like #a_horse commented.
But you don't even need CASE at all in this particular case for a boolean result. Just:
foo.name ~* '(foo|bar|baz)'
That's all.
~* being the case-insensitive regular expression match operator.
Never use SIMILAR TO:
Difference between LIKE and ~ in Postgres

How to update a text[] field with a string

I have a table with a field called tags which can contain any number of strings:
Table "public.page"
Column | Type | Modifiers
----------------------+--------------------------+----------------------------------
tags | text[] | not null default ARRAY[]::text[]
I want to add a string to the tags field - but I can't seem to get the concat function to work for me. I've tried:
update page set tags=concat('My New String',tags);
ERROR: function concat(unknown, text[]) does not exist
LINE 1: update page set tags=concat('My New String',tags) where ...
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
and
update page set tags=('My New String'||tags);
ERROR: operator is not unique: unknown || text[]
LINE 1: update page set tags = ('My New String' || tags) where w...
^
HINT: Could not choose a best candidate operator. You might need to add explicit type casts.
Any ideas?
In PostgreSQL's type system, the literal 'My New String' is not a varchar or text value, but a literal of type unknown, which can be processed as any type. (For instance, the literal for a date could be '2013-08-29'; this would not be processed as a varchar and then converted to date, it would be interpreted as a "date literal" at a very low level.)
Often, PostgreSQL can deduce the type automatically, but when it can't, you need to use one of the following to tell it that you want the literal to be treated as text:
text 'My New String' (SQL standard literal syntax)
Cast('My New String' as text) (SQL standard cast syntax, but not really a cast in this context)
'My New String'::text (PostgreSQL non-standard cast syntax, but quite readable)
In your case , the error message operator is not unique: unknown || text[] is saying that there are multiple types that Postgres could interpret the literal as, each with their own definition of the || operator.
You therefore need something like this (I've removed the unnecessary parentheses):
update page set tags = 'My New String'::text || tags;
Did you try || to concatenate?
select array['abc','def']::text[] || 'qwerty'::text;
http://www.postgresql.org/docs/current/static/functions-array.html#ARRAY-OPERATORS-TABLE
Note: this answer was in response to the OP's original (unedited) question. Other answers contain more detail relevant to the updated question.