I've got a simple schema:
export default {
title: 'hash schema',
version: 0,
primaryKey: 'hash',
type: 'object',
keyCompression: true,
properties: {
uuid: { type: 'string' },
id: { type: 'number' }
}
}
I want to have a table with a string field uuid as a primary key and I want to map it to an unique number that is automatically increased.
Is there a way to do so?
"automatically incrementing" doesn't sound very UUIDish to me. UUIDs are supposed to be random and unpredictable. You leave yourself vulnerable to the German Tank Problem
Nevertheless, you can indicate a string should be a UUID in JSON Schema by using "format": "uuid". It is only available in implementations supporting specification version draft2019-09 or later.
Related
How to form a unique constraint with multiple fields in keystonejs?
const Redemption = list({
access: allowAll,
fields: {
program: relationship({ ref: 'Program', many: false }),
type: text({ label: 'Type', validation: { isRequired: true }, isIndexed: 'unique' }),
name: text({ label: 'name', validation: { isRequired: true }, isIndexed: 'unique' }),
},
//TODO: validation to check that program, type, name form a unique constraint
})
The best way I can think to do this currently is by adding another field to the list and concatenating your other values into it using a hook. This lets you enforces uniqueness across these three values (combine) at the DB-level.
The list config (and hook) might look like this:
const Redemption = list({
access: allowAll,
fields: {
program: relationship({ ref: 'Program', many: false }),
type: text({ validation: { isRequired: true } }),
name: text({ validation: { isRequired: true } }),
compoundKey: text({
isIndexed: 'unique',
ui: {
createView: { fieldMode: 'hidden' },
itemView: { fieldMode: 'read' },
listView: { fieldMode: 'hidden' },
},
graphql: { omit: ['create', 'update'] },
}),
},
hooks: {
resolveInput: async ({ item, resolvedData }) => {
const program = resolvedData.program?.connect.id || ( item ? item?.programId : 'none');
const type = resolvedData.type || item?.type;
const name = resolvedData.name || item?.name;
resolvedData.compoundKey = `${program}-${type}-${name}`;
return resolvedData;
},
}
});
Few things to note here:
I've removed the isIndexed: 'unique' config for the main three fields. If I understand the problem you're trying to solve correctly, you actually don't want these values (on their own) to be distinct.
I've also remove the label config from your example. The label defaults to the field key so, in your example, that config is redundant.
As you can see, I've added the compoundKey field to store our composite values:
The ui settings make the field appear as uneditable in the UI
The graphql settings block updates on the API too (you could do the same thing with access control but I think just omitting the field is a bit cleaner)
And of course the unique index, which will be enforced by the DB
I've used a resolveInput hook as it lets you modify data before it's saved. To account for both create and update operations we need to consult both the resolvedData and item arguments - resolvedData gives us new/updated values (but undefined for any fields not being updated) and item give us the existing values in the DB. By combining values from both we can build the correct compound key each time and add it to the returned object.
And it works! When creating a redemption we'll be prompted for the 3 main fields (the compound key is hidden):
And the compound key is correctly set from the values entered:
Editing any of the values also updates the compound key:
Note that the compound key field is read-only for clarity.
And if we check the resultant DB structure, we can see our unique constraint being enforced:
CREATE TABLE "Redemption" (
id text PRIMARY KEY,
program text REFERENCES "Program"(id) ON DELETE SET NULL ON UPDATE CASCADE,
type text NOT NULL DEFAULT ''::text,
name text NOT NULL DEFAULT ''::text,
"compoundKey" text NOT NULL DEFAULT ''::text
);
CREATE UNIQUE INDEX "Redemption_pkey" ON "Redemption"(id text_ops);
CREATE INDEX "Redemption_program_idx" ON "Redemption"(program text_ops);
CREATE UNIQUE INDEX "Redemption_compoundKey_key" ON "Redemption"("compoundKey" text_ops);
Attempting to violate the constraint will produce an error:
If you wanted to customise this behaviour you could implement a validateInput hook and return a custom ValidationFailureError message.
I am trying the GraphQL for the first time. I have a express-graphql server connected to MySQL for hypothetical juice shops, where a owner has ability add or remove or rename the serve type.
For example
Shop A has serves like "Cute Small","The Regular" and "Extravaganza"
Where as shop B serves like "Xsmall","small","medium","large" and "Xlarge"
As the GraphQL fields are mandatory, I am unable think of solution for this particular scenario.
In short, I would love to know if there is a way to write a GraphQLObjectType where the fields can be any/not mentioned.
Snippet of a menu type, were the fields is very specific
var typeDef = new GraphQLObjectType({
name: "Menu",
fields: {
name: { type: GraphQLString },
small_serve: { type: GraphQLFloat },
regular_serve: { type: GraphQLFloat },
medium_serve: { type: GraphQLFloat },
large_serve: { type: GraphQLFloat },
},
});
GraphiQL
{
menus{
name,
small_serve,
regular_serve,
medium_serve,
large_serve
}
}
GraphQLInt only supports upto 4,5 integers
GraphQLFloat can't be a phone number,
so what can be used for phone numbers
7189008290
GraphQLString is the way to go. Since you're not doing any math operations or whatever, why wouldn't you use it? You can do something like this:
fields : () => {
id: { type: GraphQLId },
phoneNumber: { type: GraphQLString },
args: { type: GraphQLNonNull(GraphQLId) },
resolve: (_, obj, args) => ...
}
Above is a small example where you're returning a phone number as a string using client id as argument
EDIT:
GraphQLInt was't designed for phone numbers, easiest way to do it is with string. Even though you could have used GraphQLInt, you still needed some kind of validation..(ex: 000000877, not a valid phone number) . You don't need to do the validation by yourself, you can always use this package: https://github.com/chriso/validator.js/blob/master/README.md
Since it's a simple front end validator, the GraphQL performance won't be affected ;)
Here's my code from the express application for GraphQL schema:-
let data = new GraphQLObjectType({
name:"Data",
fields: {
id: {type: GraphQLID},
value: {type: GraphQLString} // TODO: allow for string as well as int
}
});
How can I make 'value' field accept a string value as well as int value so that it can be stored using the correct type?
According to your comment saying that you use NoSQL database which can place both strings and ints in the same field, you should go with GraphQLString. When creating new instance of above object, you can create a resolve method for value field, which would check if passed value is string or int (of course it would always be string because of GraphQLString type, however it can be a string like "123" which can be parsed to int) - according to this you can perform some parsing before saving in the database.
On the other hand, when you will retrieve the data from database, it will always occur as a string in the graphql representation - if this is not a case I think that this could be a simple solution.
However, if you are not satisfied with this proposition, I am afraid that you can't trick GraphQL as you want to. Every field can obtain only single type definition.
EDIT:
This solution is not valid for the question. It works only for object types and not scalars
You should look at GraphQLUnionType: http://graphql.org/graphql-js/type/#graphqluniontype
I'm not used to define types like this, but I expect it to be something like the below:
var ValueType = new GraphQLUnionType({
name: 'Value',
types: [ GraphQLString, GraphQLInt ],
resolveType(value) {
if (value instanceof string) {
return GraphQLString;
}
if (value instanceof number) {
return GraphQLInt;
}
}
});
let data = new GraphQLObjectType({
name:"Data",
fields: {
id: {type: GraphQLID},
value: {type: ValueType}
}
});
When defining a query in a schema, how do I refer to a value of an GraphQLEnumType declared previously, to use it as the default value of an argument?
Let's say I've defined following ObservationPeriod GraphQLEnumType:
observationPeriodEnum = new GraphQLEnumType {
name: "ObservationPeriod"
description: "One of the performance metrics observation periods"
values:
Daily:
value: '1D'
description: "Daily"
[…]
}
and use it as the type of query argument period:
queryRootType = new GraphQLObjectType {
name: "QueryRoot"
description: "Query entry points to the DWH."
fields:
performance:
type: performanceType
description: "Given a portfolio EID, an observation period (defaults to YTD)
and as-of date, as well as the source performance engine,
return the matching performance metrics."
args:
period:
type: observationPeriodEnum
defaultValue: observationPeriodEnum.Daily ← how to achieve this?
[…]
}
Currently I'm using the actual '1D' string value as the default value; this works:
period:
type: observationPeriodEnum
defaultValue: '1D'
But is there a way I could use the Daily symbolic name instead? I couldn't find a way to use the names within the schema itself. Is there something I overlooked?
I'm asking, because I was expecting an enum type to behave as a set of constants also, and to be able to use them like this in the schema definition:
period:
type: observationPeriodEnum
defaultValue: observationPeriodEnum.Daily
Naïve workaround:
##
# Given a GraphQLEnumType instance, this macro function injects the names
# of its enum values as keys the instance itself and returns the modified
# GraphQLEnumType instance.
#
modifiedWithNameKeys = (enumType) ->
for ev in enumType.getValues()
unless enumType[ ev.name]?
enumType[ ev.name] = ev.value
else
console.warn "SCHEMA> Enum name #{ev.name} conflicts with key of same
name on GraphQLEnumType object; it won't be injected for value lookup"
enumType
observationPeriodEnum = modifiedWithNameKeys new GraphQLEnumType {
name: "description: "Daily""
values:
[…]
which allows to use it as desired in schema definition:
period:
type: observationPeriodEnum
defaultValue: observationPeriodEnum.Daily
Of course, this modifier fullfils its promise, only as long as the enum names do not interfere with GraphQLEnumType existing method and variable names (which are currently: name, description, _values, _enumConfig, _valueLookup, _nameLookup, getValues, serialize, parseValue, _getValueLookup, _getNameLookup and toString — see definition of GraphQLEnumType class around line 687 in https://github.com/graphql/graphql-js/blob/master/src/type/definition.js#L687)
I just ran into this. My enum:
const contributorArgs = Object.assign(
{},
connectionArgs, {
sort: {
type: new GraphQLEnumType({
name: 'ContributorSort',
values: {
top: { value: 0 },
},
})
},
}
);
In my queries, I was doing:
... on Topic {
id
contributors(first: 10, sort: 'top') {
...
}
}
Turns out you just don't quote the value (which after thinking about it makes sense; it's a value in the enum type, not an actual value:
... on Topic {
id
contributors(first: 10, sort: top) {
...
}
}
It's possible to declare enum values as default inputs via the schema definition language, but it looks like you are only using the JS library APIs. You might be able to get to a solution by taking a look at the ASTs for the working example and comparing that with the AST from what your JS code is producing.
Sorry not a solution, but hope that helps!
I found a pull request adding a method .getValue() to enum types, which returns name and value. In your case this call:
observationPeriodEnum.getValue('Daily');
would return:
{
name: 'Daily',
value: '1D'
}