Skip to content

Commit 32c3eeb

Browse files
ivasilovjoshenlim
andauthored
feat: Create new schema when creating a FDW with the schema option (supabase#37205)
* Change the import foreign schema to always create a schema. * Fix the Iceberg wrapper. * Refactor the create wrapper to always create a new schema. * Remove unneeded props. * Smol fixes * Prevent double error toasts * Smol fix * Fix the wrapper creation to include the correct api key. * Fix a bug with the new api keys. * Handle both types of keys when fetching the iceberg namespaces. * Add a field to all wrappers to hold the schema name which can import foreign schema. * sq * When importing a foreign schema, save the schema in a special field. * Fix a type error. * Fix importing foreign schema overriding wrapper server options with unencrypted values * Add comment * Handle duplicate and empty schemas when importing a foreign schema. * Update the copy in the foreign schema wrappers. * Remove unnecessary code. --------- Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
1 parent 1332d30 commit 32c3eeb

18 files changed

+537
-294
lines changed

apps/studio/components/interfaces/Integrations/Integration/MarkdownContent.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export const MarkdownContent = ({ integrationId }: { integrationId: string }) =>
4242
/>
4343
)}
4444
{supportExpanding && (
45-
<div className={cn('bottom-0 z-10', !isExpanded ? 'absolute' : 'relative mt-3')}>
45+
<div className={cn('bottom-0', !isExpanded ? 'absolute' : 'relative mt-3')}>
4646
<button
4747
className="text-foreground-light hover:text-foreground underline text-sm"
4848
onClick={() => setIsExpanded(!isExpanded)}

apps/studio/components/interfaces/Integrations/Wrappers/CreateIcebergWrapperSheet.tsx

Lines changed: 77 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,17 @@ import { isEmpty } from 'lodash'
22
import { useMemo, useState } from 'react'
33
import { toast } from 'sonner'
44

5-
import SchemaEditor from 'components/interfaces/TableGridEditor/SidePanelEditor/SchemaEditor'
65
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
76
import { FormSection, FormSectionContent, FormSectionLabel } from 'components/ui/Forms/FormSection'
8-
import SchemaSelector from 'components/ui/SchemaSelector'
7+
import { useSchemaCreateMutation } from 'data/database/schema-create-mutation'
98
import { useSchemasQuery } from 'data/database/schemas-query'
109
import { useFDWCreateMutation } from 'data/fdw/fdw-create-mutation'
1110
import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
11+
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
1212
import {
1313
Button,
1414
Form,
1515
Input,
16-
Label_Shadcn_,
1716
RadioGroupStacked,
1817
RadioGroupStackedItem,
1918
Separator,
@@ -25,7 +24,6 @@ import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
2524
import { CreateWrapperSheetProps } from './CreateWrapperSheet'
2625
import InputField from './InputField'
2726
import { makeValidateRequired } from './Wrappers.utils'
28-
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
2927

3028
const FORM_ID = 'create-wrapper-form'
3129

@@ -68,12 +66,11 @@ export const CreateIcebergWrapperSheet = ({
6866
const org = useSelectedOrganization()
6967
const { mutate: sendEvent } = useSendEventMutation()
7068

71-
const [createSchemaSheetOpen, setCreateSchemaSheetOpen] = useState(false)
7269
const [selectedTarget, setSelectedTarget] = useState<Target>('S3Tables')
7370

7471
const [formErrors, setFormErrors] = useState<{ [k: string]: string }>({})
7572

76-
const { mutate: createFDW, isLoading: isCreating } = useFDWCreateMutation({
73+
const { mutateAsync: createFDW, isLoading: isCreatingWrapper } = useFDWCreateMutation({
7774
onSuccess: () => {
7875
toast.success(`Successfully created ${wrapperMeta?.label} foreign data wrapper`)
7976
onClose()
@@ -100,8 +97,10 @@ export const CreateIcebergWrapperSheet = ({
10097
}
10198
}, [wrapperMetaOriginal, selectedTarget])
10299

103-
// prefetch schemas to make sure the schema selector is populated
104-
useSchemasQuery({ projectRef: project?.ref, connectionString: project?.connectionString })
100+
const { data: schemas } = useSchemasQuery({
101+
projectRef: project?.ref!,
102+
connectionString: project?.connectionString,
103+
})
105104

106105
const initialValues = {
107106
wrapper_name: '',
@@ -113,41 +112,72 @@ export const CreateIcebergWrapperSheet = ({
113112
),
114113
}
115114

115+
const { mutateAsync: createSchema, isLoading: isCreatingSchema } = useSchemaCreateMutation()
116+
116117
const onSubmit = async (values: any) => {
117118
const validate = makeValidateRequired(wrapperMeta.server.options)
118119
const errors: any = validate(values)
119120

120121
if (values.source_schema.length === 0) {
121-
errors.source_schema = 'Please provide a source schema'
122+
errors.source_schema = 'Please provide a namespace name'
122123
}
123124
if (values.wrapper_name.length === 0) {
124125
errors.wrapper_name = 'Please provide a name for your wrapper'
125126
}
126-
if (!isEmpty(errors)) return setFormErrors(errors)
127127

128-
createFDW({
129-
projectRef: project?.ref,
130-
connectionString: project?.connectionString,
131-
wrapperMeta,
132-
formState: { ...values, server_name: `${values.wrapper_name}_server` },
133-
mode: 'schema',
134-
tables: [],
135-
sourceSchema: values.source_schema,
136-
targetSchema: values.target_schema,
137-
})
128+
if (values.target_schema.length === 0) {
129+
errors.target_schema = 'Please provide an unique target schema'
130+
}
131+
const foundSchema = schemas?.find((s) => s.name === values.target_schema)
132+
if (foundSchema) {
133+
errors.target_schema = 'This schema already exists. Please specify a unique schema name.'
134+
}
138135

139-
sendEvent({
140-
action: 'foreign_data_wrapper_created',
141-
properties: {
142-
wrapperType: wrapperMeta.label,
143-
},
144-
groups: {
145-
project: project?.ref ?? 'Unknown',
146-
organization: org?.slug ?? 'Unknown',
147-
},
148-
})
136+
setFormErrors(errors)
137+
if (!isEmpty(errors)) {
138+
return
139+
}
140+
141+
try {
142+
await createSchema({
143+
projectRef: project?.ref,
144+
connectionString: project?.connectionString,
145+
name: values.target_schema,
146+
})
147+
148+
await createFDW({
149+
projectRef: project?.ref,
150+
connectionString: project?.connectionString,
151+
wrapperMeta,
152+
formState: {
153+
...values,
154+
server_name: `${values.wrapper_name}_server`,
155+
supabase_target_schema: values.target_schema,
156+
},
157+
mode: 'schema',
158+
tables: [],
159+
sourceSchema: values.source_schema,
160+
targetSchema: values.target_schema,
161+
})
162+
163+
sendEvent({
164+
action: 'foreign_data_wrapper_created',
165+
properties: {
166+
wrapperType: wrapperMeta.label,
167+
},
168+
groups: {
169+
project: project?.ref ?? 'Unknown',
170+
organization: org?.slug ?? 'Unknown',
171+
},
172+
})
173+
} catch (error) {
174+
console.error(error)
175+
// The error will be handled by the mutation onError callback (toast.error)
176+
}
149177
}
150178

179+
const isLoading = isCreatingWrapper || isCreatingSchema
180+
151181
return (
152182
<>
153183
<div className="h-full" tabIndex={-1}>
@@ -272,7 +302,8 @@ export const CreateIcebergWrapperSheet = ({
272302
<FormSectionLabel>
273303
<p>Foreign Schema</p>
274304
<p className="text-foreground-light mt-2 w-[90%]">
275-
All wrapper tables will be created in the specified target schema.
305+
You can query your data from the foreign tables in the specified schema
306+
after the wrapper is created.
276307
</p>
277308
</FormSectionLabel>
278309
}
@@ -292,18 +323,21 @@ export const CreateIcebergWrapperSheet = ({
292323
</div>
293324
)}
294325
<div className="flex flex-col gap-2">
295-
<Label_Shadcn_ className="text-foreground-light">
296-
Target Schema
297-
</Label_Shadcn_>
298-
<SchemaSelector
299-
portal={false}
300-
size="small"
301-
selectedSchemaName={values.target_schema}
302-
onSelectSchema={(schema) => setFieldValue('target_schema', schema)}
303-
onSelectCreateSchema={() => setCreateSchemaSheetOpen(true)}
326+
<InputField
327+
key="target_schema"
328+
option={{
329+
name: 'target_schema',
330+
label: 'Specify a new schema to create all wrapper tables in',
331+
required: true,
332+
encrypted: false,
333+
secureEntry: false,
334+
}}
335+
loading={false}
336+
error={formErrors['target_schema']}
304337
/>
305338
<p className="text-foreground-lighter text-sm">
306-
Be careful not to use an API exposed schema.
339+
A new schema will be created. For security purposes, the wrapper tables
340+
from the foreign schema cannot be created within an existing schema
307341
</p>
308342
</div>
309343
</FormSectionContent>
@@ -316,7 +350,7 @@ export const CreateIcebergWrapperSheet = ({
316350
type="default"
317351
htmlType="button"
318352
onClick={onClosePanel}
319-
disabled={isCreating}
353+
disabled={isLoading}
320354
>
321355
Cancel
322356
</Button>
@@ -325,20 +359,11 @@ export const CreateIcebergWrapperSheet = ({
325359
type="primary"
326360
form={FORM_ID}
327361
htmlType="submit"
328-
disabled={isCreating}
329-
loading={isCreating}
362+
loading={isLoading}
330363
>
331364
Create wrapper
332365
</Button>
333366
</SheetFooter>
334-
<SchemaEditor
335-
visible={createSchemaSheetOpen}
336-
closePanel={() => setCreateSchemaSheetOpen(false)}
337-
onSuccess={(schema) => {
338-
setFieldValue('target_schema', schema)
339-
setCreateSchemaSheetOpen(false)
340-
}}
341-
/>
342367
</>
343368
)
344369
}}

0 commit comments

Comments
 (0)