How do I seed an mssql db using Prisma and createMany - prisma

I am using Prisma - client version 2.23.0 - with an mssql database. I am trying to use createMany to seed the db. I followed the steps from the Prisma docs exactly. I have run the prisma generate and prisma db push commands successfully. My database and tables are created and Prisma connects to it just fine. Here is the seed function I am using.
// Prisma create query to seed models in database
const count = await prisma.awDemographics.createMany({
data: [
{
appointed_officials: 'some officials',
awk_state: 'FL',
meeting_schedule: 'MWF',
pending_litigation: 'none',
possible_competition: 'none',
possible_contacts: 'none',
voting_requirements: 'none',
},
{
appointed_officials: 'some officials2',
awk_state: 'FL2',
meeting_schedule: 'MWF2',
pending_litigation: 'none2',
possible_competition: 'none2',
possible_contacts: 'none2',
voting_requirements: 'none2',
},
],
});
}
Here is the result
Result:
PrismaClientUnknownRequestError2 [PrismaClientUnknownRequestError]:
Invalid `prisma.awDemographics.createMany()` invocation:
DEFAULT or NULL are not allowed as explicit identity values.
This is a non-recoverable error which probably happens when the Prisma Query Engine has a panic.
Error: Command failed with exit code 1: ts-node --eval "
// #ts-ignore
declare const require: any
console.info('Result:')
const __seed = require('./src/prisma/seed.ts')
const __keys = Object.keys(__seed)
async function runSeed() {
// Execute "seed" named export or default export
if (__keys && __keys.length) {
if (__keys.indexOf('seed') !== -1) {
return __seed.seed()
} else if (__keys.indexOf('default') !== -1) {
return __seed.default()
}
}
}
runSeed()
.then(function (result) {
if (result) {
console.log(result)
}
})
.catch(function (e) {
console.error('Error from seed:')
throw e
})
I can seed using the create function just fine.
Here is my schema:
provider = "prisma-client-js"
previewFeatures = ["microsoftSqlServer"]
}
datasource db {
provider = "sqlserver"
url = env("DATABASE_URL")
}
model AwDemographics {
// ##map(name: "aw_demographics")
id Int #id #default(autoincrement())
appointed_officials String?
awk_state String?
meeting_schedule String?
pending_litigation String?
possible_competition String?
possible_contacts String?
voting_requirements String?
}

As suggested by Ryan in the comments, updating prisma and prisma client to version 2.24.0 fixed the issue.

Related

Integration Testing with GraphQL (Nexus, Apollo), Prisma, and PostgreSQL

I am trying to follow this tutorial to establish integration tests on our web application. Our stack currently includes Nexus, Next, Apollo, Prisma, and PostgreSQL.
I am using ApolloClient in place of GraphQLClient from graphql-request, I opted to use ApolloClient instead, especially since our web application is server less.
This is currently what I have inside the helper.ts, and the ApolloClient does work when I execute mutations. However, after executing a mutation on ApolloClient and checking if the data persists through Prisma, I get a null value.
Did I do these adjustments correctly? I am definitely missing something if Prisma is not querying correctly. Maybe there is a disconnect here between ApolloClient and Prisma or ApolloClient and the database? Any help would be much appreciated.
All of the code is below.
helper.ts
function graphqlTestContext() {
let serverInstance: ServerInfo | null = null;
return {
async before() {
const rootUrl = getRootUrl();
const httpLink = createHttpLink({
uri: rootUrl + "api/graphql",
credentials: "include",
fetch
});
const client = new ApolloClient({
// ssrMode: typeof window === "undefined",
link: httpLink,
cache: new InMemoryCache(),
});
return client;
},
async after() {
serverInstance?.server.close()
},
}
}
function prismaTestContext() {
const prismaBinary = join(__dirname, '../../', 'node_modules', '.bin', 'prisma');
let schema = '';
let databaseUrl = '';
let prismaClient: null | PrismaClient = null;
return {
async before() {
// Generate a unique schema identifier for this test context
schema = `test_${nanoid()}`;
// Generate the pg connection string for the test schema
databaseUrl = `${process.env.ROOT_DB_URL}/testing?schema=${schema}`;
// Set the required environment variable to contain the connection string
// to our database test schema
process.env.DATABASE_URL = databaseUrl;
// Run the migrations to ensure our schema has the required structure
execSync(`${prismaBinary} migrate dev`, {
env: {
...process.env,
DATABASE_URL: databaseUrl,
},
});
// Construct a new Prisma Client connected to the generated Postgres schema
prismaClient = new PrismaClient();
return prismaClient;
},
async after() {
// Drop the schema after the tests have completed
const client = new Client({
connectionString: databaseUrl,
});
await client.connect();
await client.query(`DROP SCHEMA IF EXISTS "${schema}" CASCADE`);
await client.end();
// Release the Prisma Client connection
await prismaClient?.$disconnect();
},
}
User.int.test.ts
const ctx = createTestContext();
describe("User", () => {
it("creates a new user with REGISTER_MUTATION", async () => {
const userResult = await ctx.client.mutate({
mutation: gql`
mutation Register(
$firstName: String!
$lastName: String!
$email: String!
$password: String!
) {
registerUser(
firstName: $firstName
lastName: $lastName
email: $email
password: $password
) {
user {
email
firstName
}
}
}
`,
variables: {
firstName: "FirstName",
lastName: "LastName",
email: "test#email.com",
password: "password"
}
});
expect(userResult).toMatchInlineSnapshot(`
Object {
"data": Object {
"registerUser": Object {
"__typename": "UserLoginPayload",
"user": Object {
"__typename": "User",
"email": "test#email.com",
"firstName": "FirstName",
},
},
},
}
`);
});
it("verifies that user persists", async () => {
const persistedData = await ctx.prisma.user.findMany();
expect(persistedData).toMatchInlineSnapshot(`Array []`);
});
});
The reason is because graphql server is instantiated with different prisma client with its own db. And the prismaTestContext has its own prisma client with different db url.

Postgres SET runtime variables with TypeORM, how to persist variable during the life of the connection between calls

I have NodeJS web server using GraphQL using 2 connections. One has admin access, the other crud access.
Underlying Postgres DB has a Row Level Security policy, i.e.:
ALTER TABLE main.user ENABLE ROW LEVEL SECURITY;
CREATE POLICY user_isolation_policy ON main.user USING (id = current_setting('app.current_user_id')::UUID);
Before I login a user, I need to get their id from the db, then set the current_user_id variable in Postgres session while logging in.
However, when I try to fetch users back, I am expecting to get back only the logged in user, not everyone - this is how it behaves using pgAdmin. However, here I am getting the following error:
error: error: unrecognized configuration parameter "app.current_user_id"
Here is how I log a user in:
#Resolver()
export class LoginResolver {
#Mutation(() => LoginResponse)
public async login(
#Arg('email') email: string,
#Arg('password') password: string,
#Ctx() { res }: AppContext
): Promise<LoginResponse> {
try {
// get user from the admin repo so we can get their ID
const userRepository = (await adminConnection).getRepository(User)
const user = await userRepository.findOne({ where: { email } })
if (!user) throw new Error('user not found')
// using the api repo (not admin), set the variable
User.getRepository().query(`SET app.current_user_id TO "${user.id}"`)
const isValid = await bcrypt.compare(password, user.password)
if (!isValid) throw new Error('incorrect password')
if (!user.isConfirmed) throw new Error('email not confirmed')
sendRefreshToken(res, user.createRefreshToken())
return { token: user.createAccessToken() }
} catch (error) {
throw new Error(error)
}
}
}
Here is how I try to fetch back users:
#Resolver()
export class UsersResolver {
#Authorized(UserRole.admin, UserRole.super)
#Query(() => [User])
public users(): Promise<User[]> {
return User.find()
}
}
Please note that, if I remove the policy, GraphQL runs normally without errors.
The set variable is not persisting. How do I persist the variable while the user is logged in?
This approach works for me:
import { EntityManager, getConnection, getConnectionManager, getManager } from "typeorm";
import { EventSubscriber, EntitySubscriberInterface, InsertEvent, UpdateEvent, RemoveEvent } from "typeorm";
#EventSubscriber()
export class CurrentUserSubscriber implements EntitySubscriberInterface {
// get the userId from the current http request/headers/wherever you store it (currently I'm typeorm only, not as part of nest/express app)
async setUserId(mng: EntityManager, userId: string) {
await mng.query(`SELECT set_config('app.current_user_id', '${userId}', true);`)
}
async beforeInsert(event: InsertEvent<any>) {
await this.setUserId(event.manager, 'myUserId');
}
async beforeUpdate(event: UpdateEvent<any>) {
await this.setUserId(event.manager, 'myUserId');
}
async beforeRemove(event: RemoveEvent<any>) {
await this.setUserId(event.manager, 'myUserId');
}
}
Don't forget to configure the subscribers property in ormconfig.js, e.g. :
"subscribers": [
"src/subscribers/CurrentUserSubscriber.ts",
],

Cache MongoDb connection with Next.js 10 TypeScript Project - API Route

I'm trying to convert next.js/examples/with-mongodb/util/mongodb.js to TS so I can cache and resue my connections to Mongo within a TS next.js project. I'm getting a TS error on cache.promise that says:
Type 'Promise<MongoClient | { client: MongoClient; db: Db; }>' is not assignable to type 'Promise<MongoClient>'
How should I properly declare the mongo property on global to appease the TS gods?
import { MongoClient, Db } from "mongodb";
const { DATABASE_URL, DATABASE_NAME } = process.env;
declare global {
namespace NodeJS {
interface Global {
mongo: {
conn: MongoClient | null;
promise: Promise<MongoClient> | null;
};
}
}
}
let cached = global.mongo;
if (!cached) {
cached = global.mongo = { conn: null, promise: null };
}
async function connect() {
if (cached.conn) {
return cached.conn;
}
if (!cached.promise) {
const opts = {
useNewUrlParser: true,
useUnifiedTopology: true,
};
cached.promise = MongoClient.connect(DATABASE_URL, opts).then((client) => {
return {
client,
db: client.db(DATABASE_NAME),
};
});
}
cached.conn = await cached.promise;
return cached.conn;
}
export { connect };
You don't need to cache your connection, check latest nextjs with mongodb example. The official mongodb forum experts have navigated me to this example project.
Try to use native solutions
The 'conn' property you are storing contains both MongoClient and Db.
In your global declaration for mongo, you have only included MongoClient. I have the exact same code in my project and the way I handle this is to simply create a basic type called MongoConnection which contains both. Code below.
type MongoConnection = {
client: MongoClient;
db: Db;
};
declare global {
namespace NodeJS {
interface Global {
mongo: {
conn: MongoConnection | null;
promise: Promise<MongoConnection> | null;
}
}
}
}
seems like the answer is to just make the mongo property an any like this:
declare global {
namespace NodeJS {
interface Global {
mongo: any;
}
}
}

Next JS connection with Apollo and MongoDB

I am new to Next.js and using this example from Next.js https://github.com/zeit/next.js/tree/master/examples/api-routes-apollo-server-and-client.
However, the example is silent on MongoDB integration (also I could not find any other example for the same). I have been able to make database-connection but NOT able to use it in resolvers.
My Code
pages/api/graphql.js
import { ApolloServer } from 'apollo-server-micro'
import { schema } from '../../apollo/schema'
const MongoClient = require('mongodb').MongoClient;
let db
const apolloServer = new ApolloServer({
schema,
context: async () => {
if (!db) {
try {
const client = await MongoClient.connect(uri)
db = await client.db('dbName')
const post = await Posts.findOne()
console.log(post)
// It's working fine here
}
catch (e) {
// handle any errors
}
}
return { db }
},
})
export const config = {
api: {
bodyParser: false,
},
}
export default apolloServer.createHandler({ path: '/api/graphql' })
apollo/schema.js
import {makeExecutableSchema} from 'graphql-tools';
import {typeDefs} from './type-defs';
import {resolvers} from './resolvers';
export const schema = makeExecutableSchema({
typeDefs,
resolvers
});
apollo/resolvers.js
const Items = require('./connector').Items;
export const resolvers = {
Query: {
viewer(_parent, _args, _context, _info) {
//want to populate values here, using database connection
return { id: 1, name: 'John Smith', status: 'cached' }
},
...
}
}
I am stuck in the resolvers.js part. Don't know how to get the cached database connection inside resolvers.js. If I create a new database connection file, top-level await is not supported there, so how do I proceed?
If context is a function, whatever you return from the function will be available as the context parameter in your resolver. So if you're returning { db }, that's what your context parameter will be -- in other words, you can access it as context.db inside your resolver.

apollostack/graphql-server - how to get the fields requested in a query from resolver

I am trying to figure out a clean way to work with queries and mongdb projections so I don't have to retrieve excessive information from the database.
So assuming I have:
// the query
type Query {
getUserByEmail(email: String!): User
}
And I have a User with an email and a username, to keep things simple. If I send a query and I only want to retrieve the email, I can do the following:
query { getUserByEmail(email: "test#test.com") { email } }
But in the resolver, my DB query still retrieves both username and email, but only one of those is passed back by apollo server as the query result.
I only want the DB to retrieve what the query asks for:
// the resolver
getUserByEmail(root, args, context, info) {
// check what fields the query requested
// create a projection to only request those fields
return db.collection('users').findOne({ email: args.email }, { /* projection */ });
}
Of course the problem is, getting information on what the client is requesting isn't so straightforward.
Assuming I pass in request as context - I considered using context.payload (hapi.js), which has the query string, and searching it through various .split()s, but that feels kind of dirty. As far as I can tell, info.fieldASTs[0].selectionSet.selections has the list of fields, and I could check for it's existence in there. I'm not sure how reliable this is. Especially when I start using more complex queries.
Is there a simpler way?
In case you don't use mongDB, a projection is an additional argument you pass in telling it explicitly what to retrieve:
// telling mongoDB to not retrieve _id
db.collection('users').findOne({ email: 'test#test.com' }, { _id: 0 })
As always, thanks to the amazing community.
2020-Jan answer
The current answer to getting the fields requested in a GraphQL query, is to use the graphql-parse-resolve-info library for parsing the info parameter.
The library is "a pretty complete solution and is actually used under the hood by postgraphile", and is recommended going forward by the author of the other top library for parsing the info field, graphql-fields.
Use graphql-fields
Apollo server example
const rootSchema = [`
type Person {
id: String!
name: String!
email: String!
picture: String!
type: Int!
status: Int!
createdAt: Float
updatedAt: Float
}
schema {
query: Query
mutation: Mutation
}
`];
const rootResolvers = {
Query: {
users(root, args, context, info) {
const topLevelFields = Object.keys(graphqlFields(info));
return fetch(`/api/user?fields=${topLevelFields.join(',')}`);
}
}
};
const schema = [...rootSchema];
const resolvers = Object.assign({}, rootResolvers);
// Create schema
const executableSchema = makeExecutableSchema({
typeDefs: schema,
resolvers,
});
Sure you can. This is actually the same functionality that is implemented on join-monster package for SQL based db's. There's a talk by their creator: https://www.youtube.com/watch?v=Y7AdMIuXOgs
Take a look on their info analysing code to get you started - https://github.com/stems/join-monster/blob/master/src/queryASTToSqlAST.js#L6-L30
Would love to see a projection-monster package for us mongo users :)
UPDATE:
There is a package that creates a projection object from info on npm: https://www.npmjs.com/package/graphql-mongodb-projection
You can generate MongoDB projection from info argument. Here is the sample code that you can follow
/**
* #description - Gets MongoDB projection from graphql query
*
* #return { object }
* #param { object } info
* #param { model } model - MongoDB model for referencing
*/
function getDBProjection(info, model) {
const {
schema: { obj }
} = model;
const keys = Object.keys(obj);
const projection = {};
const { selections } = info.fieldNodes[0].selectionSet;
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const isSelected = selections.some(
selection => selection.name.value === key
);
projection[key] = isSelected;
}
console.log(projection);
}
module.exports = getDBProjection;
With a few helper functions you can use it like this (typescript version):
import { parceGqlInfo, query } from "#backend";
import { GraphQLResolveInfo } from "graphql";
export const user = async (parent: unknown, args: unknown, ctx: unknown, info: GraphQLResolveInfo): Promise<User | null> => {
const { dbQueryStr } = parceGqlInfo(info, userFields, "id");
const [user] = await query(`SELECT ${dbQueryStr} FROM users WHERE id=$1;`, [1]);
return user;
};
Helper functions.
Few points:
gql_uid used as ID! string type from primary key to not change db types
required option is used for dataloaders (if field was not requested by user)
allowedFields used to filter additional fields from info like '__typename'
queryPrefix is used if you need to prefix selected fields like select u.id from users u
const userFields = [
"gql_uid",
"id",
"email"
]
// merge arrays and delete duplicates
export const mergeDedupe = <T>(arr: any[][]): T => {
// #ts-ignore
return ([...new Set([].concat(...arr))] as unknown) as T;
};
import { parse, simplify, ResolveTree } from "graphql-parse-resolve-info";
import { GraphQLResolveInfo } from "graphql";
export const getQueryFieldsFromInfo = <Required = string>(info: GraphQLResolveInfo, options: { required?: Required[] } = {}): string[] => {
const { fields } = simplify(parse(info) as ResolveTree, info.returnType) as { fields: { [key: string]: { name: string } } };
let astFields = Object.entries(fields).map(([, v]) => v.name);
if (options.required) {
astFields = mergeDedupe([astFields, options.required]);
}
return astFields;
};
export const onlyAllowedFields = <T extends string | number>(raw: T[] | readonly T[], allowed: T[] | readonly T[]): T[] => {
return allowed.filter((f) => raw.includes(f));
};
export const parceGqlInfo = (
info: GraphQLResolveInfo,
allowedFields: string[] | readonly string[],
gqlUidDbAlliasField: string,
options: { required?: string[]; queryPrefix?: string } = {}
): { pureDbFields: string[]; gqlUidRequested: boolean; dbQueryStr: string } => {
const fieldsWithGqlUid = onlyAllowedFields(getQueryFieldsFromInfo(info, options), allowedFields);
return {
pureDbFields: fieldsWithGqlUid.filter((i) => i !== "gql_uid"),
gqlUidRequested: fieldsWithGqlUid.includes("gql_uid"),
dbQueryStr: fieldsWithGqlUid
.map((f) => {
const dbQueryStrField = f === "gql_uid" ? `${gqlUidDbAlliasField}::Text AS gql_uid` : f;
return options.queryPrefix ? `${options.queryPrefix}.${dbQueryStrField}` : dbQueryStrField;
})
.join(),
};
};