diff --git a/apps/docs/components/MDX/kotlin_project_setup.mdx b/apps/docs/components/MDX/kotlin_project_setup.mdx
index c052565ede4..fbf2e6ebd09 100644
--- a/apps/docs/components/MDX/kotlin_project_setup.mdx
+++ b/apps/docs/components/MDX/kotlin_project_setup.mdx
@@ -20,15 +20,15 @@ Now we are going to set up the database schema. You can just copy/paste the SQL
scrollable
size="small"
type="underlined"
- defaultActiveId="dashboard"
+ defaultActiveId="sql"
>
-
+{/*
1. Go to the [SQL Editor](https://app.supabase.com/project/_/sql) page in the Dashboard.
2. Click **Product Management**.
3. Click **Run**.
-
+ */}
@@ -46,25 +46,8 @@ We just need to get the Project URL and `anon` key from the API settings.
### Set up Google Authentication
-From [Google Console](https://console.developers.google.com/apis/library), create a Project
-In the project, create new credential
+From the [Google Console](https://console.developers.google.com/apis/library), create a new project and add OAuth2 credentials.
-
+
-Then go to Supabase dashboard, in Authentication tab, open Providers to enable Google. Turn on toggle then copy and paste Client ID and Secret from Google Cloud
-
-
-
-
+In your [Supabase Auth settings](https://app.supabase.com/project/_/auth/providers) enable Google as a provider and set the required credentials as outlined in the [auth docs](/docs/guides/auth/social-login/auth-google).
diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts
index 4e0bdaa626a..8ff54ba58c6 100644
--- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts
+++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts
@@ -477,6 +477,7 @@ export const auth = {
url: undefined,
items: [
{ name: 'Enable Captcha Protection', url: '/guides/auth/auth-captcha' },
+ { name: 'Configuring Custom SMTP', url: '/guides/auth/auth-smtp' },
{ name: 'Managing User Data', url: '/guides/auth/managing-user-data' },
{ name: 'Multi-Factor Authentication', url: '/guides/auth/auth-mfa' },
{ name: 'Row Level Security', url: '/guides/auth/row-level-security' },
diff --git a/apps/docs/docs/ref/kotlin/introduction.mdx b/apps/docs/docs/ref/kotlin/introduction.mdx
index 7f1f3784175..914310ba347 100644
--- a/apps/docs/docs/ref/kotlin/introduction.mdx
+++ b/apps/docs/docs/ref/kotlin/introduction.mdx
@@ -18,15 +18,17 @@ This reference documents every object and method available in Supabase's Kotlin
Supported Kotlin targets:
-| **Module** | **GoTrue** | **Realtime** | **Postgrest** | **Storage** | **Functions** | **Apollo-GraphQL** |
-| -------------------------------------- | ---------- | ------------ | ------------- | ----------- | ------------- | ------------------ |
-| **JVM** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
-| **Android** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
-| **JS** _(Browser)_ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
-| **IOS** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
-| **MacOS** _(macosX64 & macosArm64)_ 🚧 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
-| **Windows** _(mingwX64)_ 🚧 | ☑️ | ✅ | ✅ | ✅ | ✅ | ❌ |
-| **Linux** _(linuxX64)_ 🚧 | ☑️ | ✅ | ✅ | ✅ | ✅ | ❌ |
+| | **GoTrue** | **Realtime** | **Postgrest** | **Storage** | **Functions** | **Apollo-GraphQL** |
+| ------------------------------------------------------------------ | ---------- | ------------ | ------------- | ----------- | ------------- | ------------------ |
+| **JVM** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| **Android** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| **JS** _(Browser, NodeJS)_ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| **IOS** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| **tvOS** _(tvosArm64, tvosX64, tvosSimulatorArm64)_ 🚧 | ☑️ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| **watchOS** _(watchosArm64, watchosX64, watchosSimulatorArm64)_ 🚧 | ☑️ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| **MacOS** _(macosX64 & macosArm64)_ 🚧 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| **Windows** _(mingwX64)_ 🚧 | ☑️ | ✅ | ✅ | ✅ | ✅ | ❌ |
+| **Linux** _(linuxX64)_ 🚧 | ☑️ | ✅ | ✅ | ✅ | ✅ | ❌ |
✅ = full support
diff --git a/apps/docs/layouts/SiteLayout.tsx b/apps/docs/layouts/SiteLayout.tsx
index b24019de139..59bead2a64d 100644
--- a/apps/docs/layouts/SiteLayout.tsx
+++ b/apps/docs/layouts/SiteLayout.tsx
@@ -8,6 +8,7 @@ import Head from 'next/head'
import { PropsWithChildren, memo } from 'react'
import Footer from '~/components/Navigation/Footer'
import { menuState, useMenuLevelId, useMenuMobileOpen } from '~/hooks/useMenuState'
+import { Announcement, LW8CountdownBanner } from 'ui'
const levelsData = {
home: {
@@ -326,6 +327,9 @@ const SiteLayout = ({ children }: PropsWithChildren<{}>) => {
Supabase Docs
+
+
+
diff --git a/apps/docs/pages/guides/ai/examples/building-chatgpt-plugins.mdx b/apps/docs/pages/guides/ai/examples/building-chatgpt-plugins.mdx
index 1f34a54c413..80d80a6cc65 100644
--- a/apps/docs/pages/guides/ai/examples/building-chatgpt-plugins.mdx
+++ b/apps/docs/pages/guides/ai/examples/building-chatgpt-plugins.mdx
@@ -153,7 +153,7 @@ And after ChatGPT receives a response from the plugin it will answer your questi
## Resources
- ChatGPT Retrieval Plugin: [github.com/openai/chatgpt-retrieval-plugin](https://github.com/openai/chatgpt-retrieval-plugin)
-- ChatGTP Plugins: [official documentation](https://platform.openai.com/docs/plugins/introduction)
+- ChatGPT Plugins: [official documentation](https://platform.openai.com/docs/plugins/introduction)
export const Page = ({ children }) =>
export default Page
diff --git a/apps/docs/pages/guides/auth/auth-captcha.mdx b/apps/docs/pages/guides/auth/auth-captcha.mdx
index 0834167875d..e36e96262bc 100644
--- a/apps/docs/pages/guides/auth/auth-captcha.mdx
+++ b/apps/docs/pages/guides/auth/auth-captcha.mdx
@@ -51,7 +51,7 @@ In the Settings page, look for the **Sitekey** section and copy the key.
## Enable Captcha protection for your Supabase project
-Navigate to the **[Authentication](https://supabase.com/dashboard/project/_/settings/auth)** page in the Supabase Dashboard and find the **Enable Captcha protection** toggle under the **Security and Protection** section.
+Navigate to the **[Auth](https://supabase.com/dashboard/project/_/settings/auth)** section of your Project Settings in the Supabase Dashboard and find the **Enable Captcha protection** toggle under the **Security and Protection** section.

diff --git a/apps/docs/pages/guides/auth/auth-helpers/auth-ui.mdx b/apps/docs/pages/guides/auth/auth-helpers/auth-ui.mdx
index 8256063a9b8..ed2aea88f97 100644
--- a/apps/docs/pages/guides/auth/auth-helpers/auth-ui.mdx
+++ b/apps/docs/pages/guides/auth/auth-helpers/auth-ui.mdx
@@ -92,7 +92,7 @@ Options are available via `queryParams`:
prompt: 'consent',
hd: 'domain.com',
}}
- onlyThirdPartyProviders={true}
+ onlyThirdPartyProviders
/>
```
diff --git a/apps/docs/pages/guides/auth/auth-smtp.mdx b/apps/docs/pages/guides/auth/auth-smtp.mdx
new file mode 100644
index 00000000000..8f30266f81f
--- /dev/null
+++ b/apps/docs/pages/guides/auth/auth-smtp.mdx
@@ -0,0 +1,39 @@
+import Layout from '~/layouts/DefaultGuideLayout'
+
+export const meta = {
+ id: 'auth-smtp',
+ title: 'Configure a Custom SMTP',
+ description: 'Moving towards production: Configuring a custom SMTP provider',
+}
+
+# Auth SMTP
+
+At present, you can trial the Supabase platform by sending up to **4** emails per hour via the built-in service. The default email service as a whole is offered on a best effort basis: we will do our best to maintain it and will review usage of the service on a regular basis to see if the email service should be continued.
+
+As you progress toward production, you may find yourself wanting for a custom SMTP service in order to increase your limits. A custom SMTP server will allow you to set your own cap on the number of emails sent per hour.
+
+Beyond rate limits, an SMTP server might also help with:
+
+- Deliverability and Reputation Management
+- Scalability
+- Analytics and Tracking
+- Compliance and Anti Spam measures
+
+## How to Set up SMTP
+
+Head over to the [Auth Settings Page](https://supabase.com/dashboard/project/_/settings/auth) and hit "Enable Custom SMTP" under the SMTP Provider section.
+
+Fill in fields below with the relevant details obtained from your custom SMTP provider:
+
+
+
+### SMTP Providers
+
+You can use Supabase Auth with any major SMTP provider of your choosing. Some SMTP providers you could consider using are:
+
+- [Twilio SendGrid](https://docs.sendgrid.com/for-developers/sending-email/integrating-with-the-smtp-api)
+- [AWS SES](https://docs.aws.amazon.com/ses/latest/dg/send-email-smtp.html)
+- [Resend](https://resend.com/docs/dashboard/emails/introduction)
+
+export const Page = ({ children }) =>
+export default Page
diff --git a/apps/docs/pages/guides/auth/phone-login/twilio.mdx b/apps/docs/pages/guides/auth/phone-login/twilio.mdx
index d4bae16558c..60277debe66 100644
--- a/apps/docs/pages/guides/auth/phone-login/twilio.mdx
+++ b/apps/docs/pages/guides/auth/phone-login/twilio.mdx
@@ -7,16 +7,42 @@ export const meta = {
video: 'https://www.youtube.com/v/akScoPO01bc',
}
-## Overview
-
In this guide we'll show you how to authenticate your users with SMS based One-Time Password (OTP) tokens.
There are two reasons to use Supabase SMS OTP tokens:
-- You want users to log in with mobile number + password, and the mobile number should be verified via SMS
-- You want users to log in with mobile number ONLY (i.e. passwordless login)
+- You want users to log in with a phone number and password, and verify the phone number on signup with SMS
+- You want users to log in with a phone number ONLY (i.e. passwordless login)
-We'll cover:
+What you'll need:
+
+- Twilio account ([sign up](https://www.twilio.com/try-twilio))
+- Supabase project (create one [here](https://supabase.com/dashboard))
+- Mobile phone capable of receiving SMS
+
+SMS Authentication can be done with either Twilio Verify or Twilio Programmable Messaging. [Twilio Verify](https://www.twilio.com/en-us/trusted-activation/verify) is a specialized OTP solution and is recommended for most developers that need over-the-phone authentication. Alternatively you can use [Twilio Programmable Messaging](https://www.twilio.com/docs/messaging) which offers generic SMS sending support.
+
+## Twilio Verify
+
+To set up Twilio Verify, you will need to:
+
+1. Create a new [verification service](https://support.twilio.com/hc/en-us/articles/360033309133-Getting-Started-with-Twilio-Verify-V2) in the Twilio dashboard.
+2. [Switch Phone Provider to Twilio Verify](https://supabase.com/dashboard/project/_/auth/providers)
+3. Configure the Twilio Verify Service ID field using the Verification Service ID obtained in 1.
+
+When using Twilio Verify, OTPs are generated by Twilio. This means that:
+
+- Unlike other providers, the OTP expiry duration and message content fields are not configurable via the Supabase dashboard. Please head to Twilio Verify to configure these settings.
+- The token remains the same during its validity period until the verification is successful. This means if your user make another request within that period, they will receive the same token.
+- Twilio Verify has a separate set of rate limits that apply. Visit Twilio's [Rate Limit and Timeouts page](https://www.twilio.com/docs/verify/api/rate-limits-and-timeouts) to find out more.
+
+
+ At this time, Twilio Verify is only supported on the `whatsapp` and `sms` channels.
+
+
+## Twilio (Programmable Messaging)
+
+In this section we'll cover:
- [Finding your Twilio credentials](#finding-your-twilio-credentials)
- [Using OTP with password based logins](#using-otp-with-password-based-logins)
@@ -349,6 +375,8 @@ let { data, error } = await supabase.auth.verifyOtp({
## Resources
+- [Reasons to use Twilio Verify](https://www.twilio.com/blog/9-reasons-to-use-the-verify-api)
+- [Twilio Verify Documentation](https://www.twilio.com/docs/verify)
- [Twilio Signup](https://www.twilio.com/try-twilio)
- [Supabase Dashboard](https://supabase.com/dashboard)
- [Supabase Row Level Security](/docs/guides/auth#row-level-security)
diff --git a/apps/docs/pages/guides/auth/social-login/auth-facebook.mdx b/apps/docs/pages/guides/auth/social-login/auth-facebook.mdx
index d0dd5855c73..f46284137b6 100644
--- a/apps/docs/pages/guides/auth/social-login/auth-facebook.mdx
+++ b/apps/docs/pages/guides/auth/social-login/auth-facebook.mdx
@@ -33,7 +33,7 @@ Setting up Facebook logins for your application consists of 3 parts:
-## Set up FaceBook Login for your Facebook App
+## Set up Facebook Login for your Facebook App
From the `Add Products to your App` screen:
diff --git a/apps/docs/pages/guides/cli/local-development.mdx b/apps/docs/pages/guides/cli/local-development.mdx
index 4faeb3f900f..1682de48d8c 100644
--- a/apps/docs/pages/guides/cli/local-development.mdx
+++ b/apps/docs/pages/guides/cli/local-development.mdx
@@ -403,8 +403,8 @@ To use Auth locally, update your project's `supabase/config.toml` file that gets
```bash supabase/config.toml
[auth.external.github]
enabled = true
-client_id = "env($SUPABASE_AUTH_GITHUB_CLIENT_ID)"
-secret = "env($SUPABASE_AUTH_GITHUB_SECRET)"
+client_id = "env(SUPABASE_AUTH_GITHUB_CLIENT_ID)"
+secret = "env(SUPABASE_AUTH_GITHUB_SECRET)"
redirect_uri = "http://localhost:54321/auth/v1/callback"
```
diff --git a/apps/docs/pages/guides/getting-started/quickstarts/kotlin.mdx b/apps/docs/pages/guides/getting-started/quickstarts/kotlin.mdx
index 55592b402aa..91f5a535fa5 100644
--- a/apps/docs/pages/guides/getting-started/quickstarts/kotlin.mdx
+++ b/apps/docs/pages/guides/getting-started/quickstarts/kotlin.mdx
@@ -4,7 +4,7 @@ import StepHikeCompact from '~/components/StepHikeCompact'
export const meta = {
title: 'Use Supabase with Android Kotlin',
subtitle:
- 'Learn how to create a Supabase project, add some sample data to your database, and query the data from a Android Kotlin app.',
+ 'Learn how to create a Supabase project, add some sample data to your database, and query the data from an Android Kotlin app.',
breadcrumb: 'Framework Quickstarts',
}
@@ -45,8 +45,7 @@ export const meta = {
- Setup environment for Android Development following official document.
- Open Android Studio > New > New Android Project
+ Open Android Studio > New > New Android Project.
@@ -55,16 +54,7 @@ export const meta = {
- Setup Supabase libraries requires some others libraries from ktor, so make sure all dependencies are added.
- Replace the dependency version placeholders `$supabase_version` and `$ktor_version` with the latest versions as shown below.
-
+ Import Supabase and all required dependencies. Replace the version placeholders `$supabase_version` and `$ktor_version` with the respective latest versions.
@@ -83,10 +73,7 @@ export const meta = {
- Open `build.gradle` (app), add serialization plugin to use annotation for data parsing. Please note that the plugin version should be the same with Kotlin version in your app.
-
-
-
+ Open the `build.gradle` (app), add the serialization plugin to use annotation for data parsing. Please note that the plugin version should be the same as the Kotlin version in your app.
@@ -105,14 +92,12 @@ export const meta = {
- You can create a whenever you need to perform some API call.
- However, it is recommended that we should provide this kind of instance by using dependency injection library like Hilt. So see the code below to provide them properly. This is better for testability and maintainability
+ You can create a Supabase client whenever you need to perform an API call. That being said, it is recommended to use a dependency injection library like [Hilt](https://developer.android.com/training/dependency-injection/hilt-android#kts).
```kotlin
- // Handy create instance when need
val client = createSupabaseClient(
supabaseUrl = "https://xyzcompany.supabase.co",
supabaseKey = "public-anon-key"
@@ -129,7 +114,7 @@ export const meta = {
-
+
@@ -161,8 +146,8 @@ export const meta = {
-
- This kind of object will be consumed by the view
+
+ This kind of object will be consumed by the view.
@@ -186,7 +171,7 @@ export const meta = {
- Usually, we will put the code of interacting with data source in repository. Create a repository:
+ Create a repository to interact with the data source.
@@ -212,7 +197,7 @@ export const meta = {
- For dependency injection, we will use Hilt. Learn more about [Dependency injection with Hilt](https://developer.android.com/training/dependency-injection/hilt-android).
+ Use [Hilt](https://developer.android.com/training/dependency-injection/hilt-android) for dependency injection.
```kotlin
@@ -229,7 +214,7 @@ export const meta = {
- To use the repository in a ViewModel, use the `@Inject` annotation.
+ Add the `@Inject` annotation to use the repository in a ViewModel.
```kotlin
@@ -269,7 +254,7 @@ export const meta = {
-
+
```kotlin
diff --git a/apps/docs/pages/guides/getting-started/tutorials/with-kotlin.mdx b/apps/docs/pages/guides/getting-started/tutorials/with-kotlin.mdx
index ecb16a0a42d..8f295c4233e 100644
--- a/apps/docs/pages/guides/getting-started/tutorials/with-kotlin.mdx
+++ b/apps/docs/pages/guides/getting-started/tutorials/with-kotlin.mdx
@@ -1,17 +1,17 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
- title: 'Build a Product Management App with Jetpack Compose in Android',
+ title: 'Build a Product Management Android App with Jetpack Compose',
description: 'Learn how to use Supabase in your Android Kotlin App.',
}
-This tutorial demonstrates how to build a basic product management app. The app demonstrates management operations, photo upload, account creation and authentication
+This tutorial demonstrates how to build a basic product management app. The app demonstrates management operations, photo upload, account creation and authentication using:
- [Supabase Database](/docs/guides/database) - a Postgres database for storing your user data and [Row Level Security](/docs/guides/auth#row-level-security) so data is protected and users can only access their own information.
-- [Supabase Auth](/docs/guides/auth) - users log in through magic links sent to their email (without having to set up passwords).
+- [Supabase Auth](/docs/guides/auth) - users log in through magic links sent to their email (without having to set up a password).
- [Supabase Storage](/docs/guides/storage) - users can upload a profile photo.
-
+
If you get stuck while working through this guide, refer to the [full example on
@@ -24,34 +24,26 @@ This tutorial demonstrates how to build a basic product management app. The app
### Create new Android project
-Open Android Studio > New Project > Base Activity (Jetpack Compose)
+Open Android Studio > New Project > Base Activity (Jetpack Compose).
-
+
### Set up API key and secret securely
-API key and secret from any services are credential information, we should not want to expose these to others because some strangers will use it for bad purpose
-
#### Create local environment secret
-Create or edit `local.properties` file at top level (same level at build.gradle of the project).
+Create or edit the `local.properties` file at the root (same level as `build.gradle`) of your project.
-> **Note**: This file should not be pushed directly to the repository, meaning that you should include this in `.gitignore` file
+> **Note**: Do not commit this file to your source control, e.g. by adding it to your `.gitignore` file!
```kotlin
-API_KEY=YOUR_SUPABASE_API_KEY
-SECRET=YOUR_SUPABASE_SECRET
+SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
SUPABASE_URL=YOUR_SUPABASE_URL
-
```
#### Read and set value to BuildConfig
-In `build.gradle (app)`, create `Properties` object and read value from `local.properties` set value for `BuildConfig` by calling `buildConfigField` method
+In your `build.gradle` (app) file, create a `Properties` object and read the values from your `local.properties` file by calling the `buildConfigField` method:
```kotlin
defaultConfig {
@@ -65,7 +57,7 @@ defaultConfig {
// Set value part
Properties properties = new Properties()
properties.load(project.rootProject.file("local.properties").newDataInputStream())
- buildConfigField("String", "API_KEY", "\"${properties.getProperty("API_KEY")}\"")
+ buildConfigField("String", "SUPABASE_ANON_KEY", "\"${properties.getProperty("SUPABASE_ANON_KEY")}\"")
buildConfigField("String", "SECRET", "\"${properties.getProperty("SECRET")}\"")
buildConfigField("String", "SUPABASE_URL", "\"${properties.getProperty("SUPABASE_URL")}\"")
}
@@ -73,43 +65,20 @@ defaultConfig {
#### Use value from `BuildConfig`
-Read value from `BuildConfig` (Remember to import the right class, it is one from your project). For example:
+Read the value from `BuildConfig`:
```kotlin
val url = BuildConfig.SUPABASE_URL
-val apiKey = BuildConfig.API_KEY
+val apiKey = BuildConfig.SUPABASE_ANON_KEY
```
### Set up Supabase dependencies
-
+
-In build.gradle (app) file, add these dependencies then press Sync now. Replace the dependency version placeholders `$supabase_version` and `$ktor_version` with the latest versions as shown below.
-
-
+In the `build.gradle` (app) file, add these dependencies then press "Sync now". Replace the dependency version placeholders `$supabase_version` and `$ktor_version` with their respective latest versions.
```kotlin
-// Supabase setup
implementation "io.github.jan-tennert.supabase:postgrest-kt:$supabase_version"
implementation "io.github.jan-tennert.supabase:storage-kt:$supabase_version"
implementation "io.github.jan-tennert.supabase:gotrue-kt:$supabase_version"
@@ -118,11 +87,8 @@ implementation "io.ktor:ktor-client-core:$ktor_version"
implementation "io.ktor:ktor-utils:$ktor_version"
```
-Also in build.gradle (app) file, add plugin for serialization. The version of this plugin should be the same with Kotlin version
+Also in the `build.gradle` (app) file, add the plugin for serialization. The version of this plugin should be the same as your Kotlin version.
-
-
-
```kotlin
plugins {
...
@@ -133,7 +99,7 @@ plugins {
### Set up Hilt for dependency injection
-In build.gradle (app) file, add these
+In the `build.gradle` (app) file, add the following:
```kotlin
implementation "com.google.dagger:hilt-android:$hilt_version"
@@ -141,7 +107,7 @@ annotationProcessor "com.google.dagger:hilt-compiler:$hilt_version"
implementation("androidx.hilt:hilt-navigation-compose:1.0.0")
```
-Create new `ManageProductApplication.kt` class extending Application with `@HiltAndroidApp` annotation
+Create a new `ManageProductApplication.kt` class extending Application with `@HiltAndroidApp` annotation:
```kotlin
// ManageProductApplication.kt
@@ -149,7 +115,7 @@ Create new `ManageProductApplication.kt` class extending Application with `@Hilt
class ManageProductApplication: Application()
```
-Open AndroidManifest.xml file, update name property of Application tag
+Open the `AndroidManifest.xml` file, update name property of Application tag:
```xml
- By the time this page is written, there might be some changes in the code to make it more simple.
- If you face any problem when implement, feel free to refer the example repository above
-
-
-Create ProductListViewModel
+Create a `ProductListViewModel`:
```kotlin
-
@HiltViewModel
class ProductListViewModel @Inject constructor(
private val productRepository: ProductRepository,
@@ -463,7 +421,7 @@ private val productRepository: ProductRepository,
```
-Create ProductListScreen.kt
+Create the `ProductListScreen.kt`:
```kotlin
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
@@ -581,7 +539,7 @@ private fun AddProductButton(
}
```
-Create ProductDetailsViewModel.kt
+Create the `ProductDetailsViewModel.kt`:
```kotlin
@@ -654,7 +612,7 @@ class ProductDetailsViewModel @Inject constructor(
}
```
-Create ProductDetailsScreen.kt
+Create the `ProductDetailsScreen.kt`:
```kotlin
@OptIn(ExperimentalCoilApi::class)
@@ -826,7 +784,7 @@ private fun uriToByteArray(contentResolver: ContentResolver, uri: Uri): ByteArra
}
```
-Create AddProductScreen
+Create a AddProductScreen:
```kotlin
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@@ -885,7 +843,7 @@ fun AddProductScreen(
}
```
-Create AddProductViewModel.kt
+Create the `AddProductViewModel.kt`:
```kotlin
@HiltViewModel
@@ -917,7 +875,7 @@ class AddProductViewModel @Inject constructor(
}
```
-Create SignUpViewModel
+Create a `SignUpViewModel`:
```kotlin
@HiltViewModel
@@ -950,7 +908,7 @@ class SignUpViewModel @Inject constructor(
}
```
-Create SignUpScreen.kt
+Create the `SignUpScreen.kt`:
```kotlin
@Composable
@@ -1048,7 +1006,7 @@ fun SignUpScreen(
}
```
-Create SignInViewModel
+Create a `SignInViewModel`:
```kotlin
@HiltViewModel
@@ -1088,7 +1046,7 @@ class SignInViewModel @Inject constructor(
}
```
-Create SignInScreen.kt
+Create the `SignInScreen.kt`:
```kotlin
@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class)
@@ -1203,10 +1161,9 @@ fun SignInScreen(
}
```
-Create successful screen
-Implement Sign In Success screen
+### Create the success screen
-Create new Empty Activity and in AndroidManifest.xml, add deep link, make sure `scheme` and `host` are the same with ones when set up GoTrue instance
+Implement a "Sign In" success screen. Create new "Empty Activity" and in `AndroidManifest.xml`, add a deep link, make sure `scheme` and `host` are the same as the ones set in the GoTrue instance.
```xml
diff --git a/apps/docs/pages/guides/platform/going-into-prod.mdx b/apps/docs/pages/guides/platform/going-into-prod.mdx
index c3ac2663e8c..6e8bd43de81 100644
--- a/apps/docs/pages/guides/platform/going-into-prod.mdx
+++ b/apps/docs/pages/guides/platform/going-into-prod.mdx
@@ -21,7 +21,7 @@ After developing your project and deciding it's Production Ready, you should run
- Go to the Authentication > Policies page in the Supabase Dashboard to enable RLS and create security policies.
- Go to the Database > Replication page in the Supabase Dashboard to manage replication tables.
- Enable 2FA on GitHub. Since your GitHub account gives you administrative rights to your Supabase project, you should protect it with a strong password and 2FA using a U2F key or a TOTP app.
-- Ensure email confirmations are enabled in the `Auth > Settings` page.
+- Ensure email confirmations are enabled in the `Settings > Auth` page.
- Use a custom SMTP server for auth emails so that your users can see that the mails are coming from a trusted domain (preferably the same domain that your app is hosted on). Grab SMTP credentials from any major email provider such as SendGrid, AWS SES, etc.
- Think hard about how _you_ would abuse your service as an attacker, and mitigate.
- Review these [common cybersecurity threats](https://auth0.com/docs/security/prevent-threats).
@@ -39,9 +39,9 @@ After developing your project and deciding it's Production Ready, you should run
## Availability
-- Use your own SMTP credentials so that you have full control over the deliverability of your transactional auth emails (see Auth > Settings)
- - you can grab SMTP credentials from any major email provider such as SendGrid, AWS SES, etc.
- - The default rate limit for auth emails provided by Supabase is 30 new users per hour, if doing a major public announcement you will likely require more than this.
+- Use your own SMTP credentials so that you have full control over the deliverability of your transactional auth emails (see Settings > Auth)
+ - you can grab SMTP credentials from any major email provider such as SendGrid, AWS SES, etc. You can refer to our [SMTP guide](/docs/guides/auth/auth-smtp) for more details.
+ - The default rate limit for auth emails when using a custom SMTP provider is 30 new users per hour, if doing a major public announcement you will likely require more than this.
- If your application is on the free plan and is **not** expected to be queried at least once every 7 days, then it may be paused by Supabase to save on server resources.
- You can restore paused projects from the Supabase dashboard.
- Upgrade to Pro to guarantee that your project will not be paused for inactivity.
diff --git a/apps/docs/pages/guides/self-hosting/docker.mdx b/apps/docs/pages/guides/self-hosting/docker.mdx
index 756081859fa..d3d326c5c11 100644
--- a/apps/docs/pages/guides/self-hosting/docker.mdx
+++ b/apps/docs/pages/guides/self-hosting/docker.mdx
@@ -48,14 +48,10 @@ We can use your JWT Secret to generate new `anon` and `service` API keys using t
### Update API Keys
-Replace the values in these files:
+Replace the values the `.env` file:
-- `.env`:
- `ANON_KEY` - replace with an `anon` key
- `SERVICE_ROLE_KEY` - replace with a `service` key
-- `volumes/api/kong.yml`
- - `anon` - replace with an `anon` key
- - `service_role` - replace with a `service` key
### Update Secrets
diff --git a/apps/docs/pages/learn/auth-deep-dive/auth-gotrue.mdx b/apps/docs/pages/learn/auth-deep-dive/auth-gotrue.mdx
index a9531106ed8..85d22733009 100644
--- a/apps/docs/pages/learn/auth-deep-dive/auth-gotrue.mdx
+++ b/apps/docs/pages/learn/auth-deep-dive/auth-gotrue.mdx
@@ -40,7 +40,7 @@ curl -X POST 'https://.supabase.co/auth/v1/magiclink' \
}'
```
-Gotrue is responsible for issuing access tokens for your users, sends confirmation, magic-link, and password recovery emails (by default we send these from a Supabase SMTP server, but you can easily plug in your own inside the dashboard at Auth > Settings) and also transacting with third party OAuth providers to get basic user data.
+Gotrue is responsible for issuing access tokens for your users, sends confirmation, magic-link, and password recovery emails (by default we send these from a Supabase SMTP server, but you can easily plug in your own inside the dashboard at Project Settings > Auth) and also transacting with third party OAuth providers to get basic user data.
The community even recently built in the functionality to request custom OAuth scopes, if your users need to interact more closely with the provider. See the scopes parameter here: [https://github.com/supabase/gotrue#get-authorize](https://github.com/supabase/gotrue#get-authorize).
diff --git a/apps/docs/public/img/deeplink-setting.png b/apps/docs/public/img/deeplink-setting.png
index a0111ae04f8..b34ff0a13b8 100644
Binary files a/apps/docs/public/img/deeplink-setting.png and b/apps/docs/public/img/deeplink-setting.png differ
diff --git a/apps/docs/public/img/guides/auth-smtp/smtp.png b/apps/docs/public/img/guides/auth-smtp/smtp.png
new file mode 100644
index 00000000000..b111b866089
Binary files /dev/null and b/apps/docs/public/img/guides/auth-smtp/smtp.png differ
diff --git a/apps/docs/public/img/guides/kotlin/android-studio-new-project.png b/apps/docs/public/img/guides/kotlin/android-studio-new-project.png
new file mode 100644
index 00000000000..70581407182
Binary files /dev/null and b/apps/docs/public/img/guides/kotlin/android-studio-new-project.png differ
diff --git a/apps/docs/public/img/guides/kotlin/google-cloud-oauth-credentials-create.png b/apps/docs/public/img/guides/kotlin/google-cloud-oauth-credentials-create.png
new file mode 100644
index 00000000000..724d07fb63b
Binary files /dev/null and b/apps/docs/public/img/guides/kotlin/google-cloud-oauth-credentials-create.png differ
diff --git a/apps/docs/public/img/guides/kotlin/gradle-dependencies.png b/apps/docs/public/img/guides/kotlin/gradle-dependencies.png
new file mode 100644
index 00000000000..fdb4c035935
Binary files /dev/null and b/apps/docs/public/img/guides/kotlin/gradle-dependencies.png differ
diff --git a/apps/docs/public/img/guides/kotlin/manage-product-cover.png b/apps/docs/public/img/guides/kotlin/manage-product-cover.png
new file mode 100644
index 00000000000..5986b8bc9fc
Binary files /dev/null and b/apps/docs/public/img/guides/kotlin/manage-product-cover.png differ
diff --git a/apps/www/_blog/2023-04-07-designing-with-ai-midjourney.mdx b/apps/www/_blog/2023-04-07-designing-with-ai-midjourney.mdx
index 07f29e5190f..d15b4143e63 100644
--- a/apps/www/_blog/2023-04-07-designing-with-ai-midjourney.mdx
+++ b/apps/www/_blog/2023-04-07-designing-with-ai-midjourney.mdx
@@ -57,7 +57,7 @@ And voila, we had a baseline for generating variations:
The final visual that you see everywhere won us over because of its vibrant swirls. While each swirl looks slightly different, a consistent style is still maintained throughout.
-Fortunately, we didn't require full-width images for each ticket. Upscaled MidJourney images sufficed. Nevertheless, we had to recreate the main visual on the landing page in vectors to use it effectively. You can view it in full glory at [https://supabase.com/launch-week](https://supabase.com/launch-week).
+Fortunately, we didn't require full-width images for each ticket. Upscaled MidJourney images sufficed. Nevertheless, we had to recreate the main visual on the landing page in vectors to use it effectively. You can view it in full glory at [https://supabase.com/launch-week/7](https://supabase.com/launch-week/7).

@@ -258,13 +258,13 @@ const channel = supabase
.subscribe()
```
-Interested to know how this fits within your Next.js application? Find the code [here](https://github.com/supabase/supabase/blob/master/apps/www/components/LaunchWeek/Ticket/TicketForm.tsx#L70-L102).
+Interested to know how this fits within your Next.js application? Find the code [here](https://github.com/supabase/supabase/blob/master/apps/www/components/LaunchWeek/7/Ticket/TicketForm.tsx#L70-L102).
## Displaying the images
-We have some images, so how can we now display them somewhere? [@Francesco Sansalvadore](https://twitter.com/frnk_snslvdr) saw one of our team members trying to “swipe” on the tickets slider component on the [launch week page](https://supabase.com/launch-week/tickets) and he thought, why not feature all the fantastic people who generated tickets on a single page?
+We have some images, so how can we now display them somewhere? [@Francesco Sansalvadore](https://twitter.com/frnk_snslvdr) saw one of our team members trying to “swipe” on the tickets slider component on the [launch week page](https://supabase.com/launch-week/7/tickets) and he thought, why not feature all the fantastic people who generated tickets on a single page?
-We built a [ticket wall](https://supabase.com/launch-week/tickets) that you can scroll endlessly. We approached it with an infinite scroll technique, lazy-loading a few tickets at a time.
+We built a [ticket wall](https://supabase.com/launch-week/7/tickets) that you can scroll endlessly. We approached it with an infinite scroll technique, lazy-loading a few tickets at a time.

diff --git a/apps/www/components/CTABanner/index.tsx b/apps/www/components/CTABanner/index.tsx
index fb08eb5630e..ff1f2ddec1a 100644
--- a/apps/www/components/CTABanner/index.tsx
+++ b/apps/www/components/CTABanner/index.tsx
@@ -1,18 +1,26 @@
import { Button } from 'ui'
-const CTABanner = (props: any) => {
- const { darkerBg } = props
+interface Props {
+ className?: string
+ darkerBg?: boolean
+}
+
+const CTABanner = ({ darkerBg, className }: Props) => {
return (
Build in a weekend,
- scale to millions
+
+ {' '}
+ scale to millions
+
)
diff --git a/apps/www/components/LaunchWeek/Ticket/TicketNumber.tsx b/apps/www/components/LaunchWeek/7/Ticket/TicketNumber.tsx
similarity index 100%
rename from apps/www/components/LaunchWeek/Ticket/TicketNumber.tsx
rename to apps/www/components/LaunchWeek/7/Ticket/TicketNumber.tsx
diff --git a/apps/www/components/LaunchWeek/Ticket/TicketProfile.tsx b/apps/www/components/LaunchWeek/7/Ticket/TicketProfile.tsx
similarity index 100%
rename from apps/www/components/LaunchWeek/Ticket/TicketProfile.tsx
rename to apps/www/components/LaunchWeek/7/Ticket/TicketProfile.tsx
diff --git a/apps/www/components/LaunchWeek/Ticket/TicketVisual.tsx b/apps/www/components/LaunchWeek/7/Ticket/TicketVisual.tsx
similarity index 98%
rename from apps/www/components/LaunchWeek/Ticket/TicketVisual.tsx
rename to apps/www/components/LaunchWeek/7/Ticket/TicketVisual.tsx
index be31e8c231c..7dffd9a3439 100644
--- a/apps/www/components/LaunchWeek/Ticket/TicketVisual.tsx
+++ b/apps/www/components/LaunchWeek/7/Ticket/TicketVisual.tsx
@@ -2,7 +2,7 @@ import styles from './ticket-visual.module.css'
import TicketProfile from './TicketProfile'
import TicketNumber from './TicketNumber'
import Tilt from 'vanilla-tilt'
-import useConfData from '~/components/LaunchWeek/Ticket/hooks/use-conf-data'
+import useConfData from '~/components/LaunchWeek/hooks/use-conf-data'
import TicketHeader from './TicketHeader'
import { useEffect, useRef, useState } from 'react'
import Image from 'next/image'
diff --git a/apps/www/components/LaunchWeek/Ticket/conf-container.module.css b/apps/www/components/LaunchWeek/7/Ticket/conf-container.module.css
similarity index 100%
rename from apps/www/components/LaunchWeek/Ticket/conf-container.module.css
rename to apps/www/components/LaunchWeek/7/Ticket/conf-container.module.css
diff --git a/apps/www/components/LaunchWeek/Ticket/conf-container.tsx b/apps/www/components/LaunchWeek/7/Ticket/conf-container.tsx
similarity index 100%
rename from apps/www/components/LaunchWeek/Ticket/conf-container.tsx
rename to apps/www/components/LaunchWeek/7/Ticket/conf-container.tsx
diff --git a/apps/www/components/LaunchWeek/Ticket/form.module.css b/apps/www/components/LaunchWeek/7/Ticket/form.module.css
similarity index 100%
rename from apps/www/components/LaunchWeek/Ticket/form.module.css
rename to apps/www/components/LaunchWeek/7/Ticket/form.module.css
diff --git a/apps/www/components/LaunchWeek/Ticket/form.tsx b/apps/www/components/LaunchWeek/7/Ticket/form.tsx
similarity index 96%
rename from apps/www/components/LaunchWeek/Ticket/form.tsx
rename to apps/www/components/LaunchWeek/7/Ticket/form.tsx
index d8a9c9dbc3d..15b20e2edb8 100644
--- a/apps/www/components/LaunchWeek/Ticket/form.tsx
+++ b/apps/www/components/LaunchWeek/7/Ticket/form.tsx
@@ -1,10 +1,10 @@
import { useState, useCallback } from 'react'
import cn from 'classnames'
-import useConfData from '~/components/LaunchWeek/Ticket/hooks/use-conf-data'
+import useConfData from '~/components/LaunchWeek/hooks/use-conf-data'
import { useRouter } from 'next/router'
-import styleUtils from './utils.module.css'
+import styleUtils from '../../utils.module.css'
import styles from './form.module.css'
-import useEmailQueryParam from '~/components/LaunchWeek/Ticket/hooks/use-email-query-param'
+import useEmailQueryParam from '~/components/LaunchWeek/hooks/use-email-query-param'
import { IconLoader } from '~/../../packages/ui'
type FormState = 'default' | 'loading' | 'error'
diff --git a/apps/www/components/LaunchWeek/Ticket/loading-dots.module.css b/apps/www/components/LaunchWeek/7/Ticket/loading-dots.module.css
similarity index 100%
rename from apps/www/components/LaunchWeek/Ticket/loading-dots.module.css
rename to apps/www/components/LaunchWeek/7/Ticket/loading-dots.module.css
diff --git a/apps/www/components/LaunchWeek/Ticket/loading-dots.tsx b/apps/www/components/LaunchWeek/7/Ticket/loading-dots.tsx
similarity index 100%
rename from apps/www/components/LaunchWeek/Ticket/loading-dots.tsx
rename to apps/www/components/LaunchWeek/7/Ticket/loading-dots.tsx
diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-actions.module.css b/apps/www/components/LaunchWeek/7/Ticket/ticket-actions.module.css
similarity index 100%
rename from apps/www/components/LaunchWeek/Ticket/ticket-actions.module.css
rename to apps/www/components/LaunchWeek/7/Ticket/ticket-actions.module.css
diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-copy.module.css b/apps/www/components/LaunchWeek/7/Ticket/ticket-copy.module.css
similarity index 100%
rename from apps/www/components/LaunchWeek/Ticket/ticket-copy.module.css
rename to apps/www/components/LaunchWeek/7/Ticket/ticket-copy.module.css
diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-copy.tsx b/apps/www/components/LaunchWeek/7/Ticket/ticket-copy.tsx
similarity index 98%
rename from apps/www/components/LaunchWeek/Ticket/ticket-copy.tsx
rename to apps/www/components/LaunchWeek/7/Ticket/ticket-copy.tsx
index c228e815a67..2286aae4293 100644
--- a/apps/www/components/LaunchWeek/Ticket/ticket-copy.tsx
+++ b/apps/www/components/LaunchWeek/7/Ticket/ticket-copy.tsx
@@ -1,7 +1,7 @@
import { useEffect, useState, useRef } from 'react'
import cn from 'classnames'
import { SITE_URL } from '~/lib/constants'
-import styleUtils from './utils.module.css'
+import styleUtils from '../../utils.module.css'
import { IconCopy, IconCheck } from 'ui'
type Props = {
diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-form.module.css b/apps/www/components/LaunchWeek/7/Ticket/ticket-form.module.css
similarity index 100%
rename from apps/www/components/LaunchWeek/Ticket/ticket-form.module.css
rename to apps/www/components/LaunchWeek/7/Ticket/ticket-form.module.css
diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-image.module.css b/apps/www/components/LaunchWeek/7/Ticket/ticket-image.module.css
similarity index 100%
rename from apps/www/components/LaunchWeek/Ticket/ticket-image.module.css
rename to apps/www/components/LaunchWeek/7/Ticket/ticket-image.module.css
diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-image.tsx b/apps/www/components/LaunchWeek/7/Ticket/ticket-image.tsx
similarity index 100%
rename from apps/www/components/LaunchWeek/Ticket/ticket-image.tsx
rename to apps/www/components/LaunchWeek/7/Ticket/ticket-image.tsx
diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-info-footer.tsx b/apps/www/components/LaunchWeek/7/Ticket/ticket-info-footer.tsx
similarity index 90%
rename from apps/www/components/LaunchWeek/Ticket/ticket-info-footer.tsx
rename to apps/www/components/LaunchWeek/7/Ticket/ticket-info-footer.tsx
index 7dd3b821630..375223f161c 100644
--- a/apps/www/components/LaunchWeek/Ticket/ticket-info-footer.tsx
+++ b/apps/www/components/LaunchWeek/7/Ticket/ticket-info-footer.tsx
@@ -1,6 +1,6 @@
import styles from './ticket-info.module.css'
import cn from 'classnames'
-import { DATE, SITE_URL } from '~/lib/constants'
+import { LW7_DATE, SITE_URL } from '~/lib/constants'
const siteUrl = new URL(SITE_URL)
const siteUrlForTicket = `${siteUrl.host}${siteUrl.pathname}`.replace(/\/$/, '')
@@ -16,7 +16,7 @@ export default function TicketInfoFooter({
golden && '!text-white'
}`}
>
-
+ Join us in a week of announcing new features.
+ Connect with GitHub to generate your unique ticket and contribute to the constellation.
+
+ {' '}
+ You might also win{' '}
+
+ swag
+
+ .
+
+
We worked with more than 30 content creators from around the world to drop a mountain
of content simultaneously!
@@ -1217,7 +1212,7 @@ export default function launchweek() {
diff --git a/studio/components/layouts/SQLEditorLayout/SQLEditorMenu.tsx b/studio/components/layouts/SQLEditorLayout/SQLEditorMenu.tsx
index c83802db398..0c07827d53c 100644
--- a/studio/components/layouts/SQLEditorLayout/SQLEditorMenu.tsx
+++ b/studio/components/layouts/SQLEditorLayout/SQLEditorMenu.tsx
@@ -29,6 +29,8 @@ const SideBarContent = observer(() => {
const snap = useSqlEditorStateSnapshot()
const { isLoading, isSuccess } = useSqlSnippetsQuery(ref, {
+ refetchOnWindowFocus: false,
+ staleTime: 300, // 5 minutes
onSuccess(data) {
if (ref) snap.setRemoteSnippets(data.snippets, ref)
},
diff --git a/studio/components/layouts/Scaffold.tsx b/studio/components/layouts/Scaffold.tsx
index 92c695d0e85..bcf1cfd9bd3 100644
--- a/studio/components/layouts/Scaffold.tsx
+++ b/studio/components/layouts/Scaffold.tsx
@@ -4,6 +4,8 @@ import { cn } from 'ui'
const maxWidthClasses = 'mx-auto w-full max-w-[1600px]'
const paddingClasses = 'px-6 lg:px-14 xl:px-28 2xl:px-32'
+const maxWidthClassesColumn = 'min-w-[420px]'
+
// lg: pt - 8
const ScaffoldHeader = React.forwardRef>(
@@ -45,6 +47,18 @@ const ScaffoldSection = React.forwardRef>(
+ ({ className, ...props }, ref) => {
+ return (
+
+ )
+ }
+)
+
const ScaffoldSectionDetail = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes
@@ -107,6 +121,7 @@ ScaffoldTitle.displayName = 'ScaffoldTitle'
ScaffoldContainer.displayName = 'ScaffoldContainer'
ScaffoldDivider.displayName = 'ScaffoldDivider'
ScaffoldSection.displayName = 'ScaffoldSection'
+ScaffoldColumn.displayName = 'ScaffoldColumn'
ScaffoldSectionDetail.displayName = 'ScaffoldSectionDetail'
ScaffoldSectionContent.displayName = 'ScaffoldSectionContent'
ScaffoldFilterAndContent.displayName = 'ScaffoldFilterAndContent'
@@ -120,6 +135,7 @@ export {
ScaffoldContainer,
ScaffoldDivider,
ScaffoldSection,
+ ScaffoldColumn,
ScaffoldSectionDetail,
ScaffoldSectionContent,
ScaffoldFilterAndContent,
diff --git a/studio/data/database/database-query-constants.ts b/studio/data/database/database-query-constants.ts
index 1030afd2d68..1506830a48a 100644
--- a/studio/data/database/database-query-constants.ts
+++ b/studio/data/database/database-query-constants.ts
@@ -20,36 +20,75 @@ export enum CONSTRAINT_TYPE {
}
// Derived from https://github.com/MichaelDBA/pg_get_tabledef
+// NOTE: when updating, \n must be replaced with \\n in the SQL below
export const CREATE_PG_GET_TABLEDEF_SQL = minify(
/* SQL */ `
- do $$
+ do $$
<>
DECLARE
cnt int;
BEGIN
SELECT count(*) into cnt
- FROM pg_catalog.pg_type t LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace WHERE (t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid))
+ FROM pg_catalog.pg_type t LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace WHERE (t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid))
AND NOT EXISTS(SELECT 1 FROM pg_catalog.pg_type el WHERE el.oid = t.typelem AND el.typarray = t.oid)
AND n.nspname <> 'pg_catalog' AND n.nspname <> 'information_schema' AND pg_catalog.pg_type_is_visible(t.oid)
+ -- AND pg_catalog.format_type(t.oid, NULL) in ('tabledef_fkeys','tabledef_trigs', 'tabledefs');
AND pg_catalog.format_type(t.oid, NULL) in ('tabledefs');
IF cnt = 0 THEN
- CREATE TYPE pg_temp.tabledefs AS ENUM ('FKEYS_INTERNAL', 'FKEYS_EXTERNAL', 'FKEYS_COMMENTED', 'FKEYS_NONE', 'INCLUDE_TRIGGERS', 'NO_TRIGGERS');
+ RAISE INFO 'Creating custom types.';
+ CREATE TYPE pg_temp.tabledefs AS ENUM ('PKEY_INTERNAL','PKEY_EXTERNAL','FKEYS_INTERNAL', 'FKEYS_EXTERNAL', 'FKEYS_COMMENTED', 'FKEYS_NONE', 'INCLUDE_TRIGGERS', 'NO_TRIGGERS');
END IF;
end first_block $$;
-
- create
- or replace function pg_temp.pg_get_tabledef (
+
+ -- DROP FUNCTION pg_temp.pg_get_coldef(text,text,text,boolean);
+ CREATE OR REPLACE FUNCTION pg_temp.pg_get_coldef(
+ in_schema text,
+ in_table text,
+ in_column text,
+ oldway boolean default False
+ )
+ RETURNS text
+ LANGUAGE plpgsql VOLATILE
+ AS
+ $$
+ DECLARE
+ coldef text;
+ BEGIN
+ IF oldway THEN
+ SELECT pg_catalog.format_type(a.atttypid, a.atttypmod) INTO coldef FROM pg_namespace n, pg_class c, pg_attribute a, pg_type t
+ WHERE n.nspname = in_schema AND n.oid = c.relnamespace AND c.relname = in_table AND a.attname = in_column and a.attnum > 0 AND a.attrelid = c.oid AND a.atttypid = t.oid ORDER BY a.attnum;
+ ELSE
+ -- a.attrelid::regclass::text, a.attname
+ SELECT CASE WHEN a.atttypid = ANY ('{int,int8,int2}'::regtype[]) AND EXISTS (SELECT FROM pg_attrdef ad WHERE ad.adrelid = a.attrelid AND ad.adnum = a.attnum AND
+ pg_get_expr(ad.adbin, ad.adrelid) = 'nextval(''' || (pg_get_serial_sequence (a.attrelid::regclass::text, a.attname))::regclass || '''::regclass)') THEN CASE a.atttypid
+ WHEN 'int'::regtype THEN 'serial' WHEN 'int8'::regtype THEN 'bigserial' WHEN 'int2'::regtype THEN 'smallserial' END ELSE format_type(a.atttypid, a.atttypmod) END AS data_type
+ INTO coldef FROM pg_namespace n, pg_class c, pg_attribute a, pg_type t
+ WHERE n.nspname = in_schema AND n.oid = c.relnamespace AND c.relname = in_table AND a.attname = in_column and a.attnum > 0 AND a.attrelid = c.oid AND a.atttypid = t.oid ORDER BY a.attnum;
+ END IF;
+ RETURN coldef;
+ END;
+ $$;
+
+ -- SELECT * FROM pg_temp.pg_get_tabledef('sample', 'address', false);
+ DROP FUNCTION IF EXISTS pg_temp.pg_get_tabledef(character varying,character varying,boolean,tabledefs[]);
+ CREATE OR REPLACE FUNCTION pg_temp.pg_get_tabledef(
in_schema varchar,
in_table varchar,
_verbose boolean,
- variadic arr pg_temp.tabledefs[] default '{}'::pg_temp.tabledefs[]
- ) returns text language plpgsql volatile as $$
+ VARIADIC arr pg_temp.tabledefs[] DEFAULT '{}':: pg_temp.tabledefs[]
+ )
+ RETURNS text
+ LANGUAGE plpgsql VOLATILE
+ AS
+ $$
DECLARE
+ v_qualified text;
v_table_ddl text;
v_table_oid int;
v_colrec record;
v_constraintrec record;
+ v_trigrec record;
v_indexrec record;
v_primary boolean := False;
v_constraint_name text;
@@ -58,28 +97,32 @@ export const CREATE_PG_GET_TABLEDEF_SQL = minify(
v_partition_key text := '';
v_partbound text;
v_parent text;
+ v_parent_schema text;
v_persist text;
- v_temp text := '';
+ v_temp text := '';
v_relopts text;
v_tablespace text;
v_pgversion int;
+ bSerial boolean;
bPartition boolean;
bInheritance boolean;
bRelispartition boolean;
constraintarr text[] := '{}';
constraintelement text;
bSkip boolean;
- bVerbose boolean := False;
-
- -- assume defaults for ENUMs at the getgo
- fkcnt int := 0;
- trigcnt int := 0;
- fktype pg_temp.tabledefs := 'FKEYS_INTERNAL';
- trigtype pg_temp.tabledefs := 'NO_TRIGGERS';
+ bVerbose boolean := False;
+
+ -- assume defaults for ENUMs at the getgo
+ pkcnt int := 0;
+ fkcnt int := 0;
+ trigcnt int := 0;
+ pktype tabledefs := 'PKEY_INTERNAL';
+ fktype tabledefs := 'FKEYS_INTERNAL';
+ trigtype tabledefs := 'NO_TRIGGERS';
arglen integer;
- vargs text;
- avarg pg_temp.tabledefs;
-
+ vargs text;
+ avarg tabledefs;
+
-- exception variables
v_ret text;
v_diag1 text;
@@ -88,10 +131,12 @@ export const CREATE_PG_GET_TABLEDEF_SQL = minify(
v_diag4 text;
v_diag5 text;
v_diag6 text;
-
+
BEGIN
+ v_qualified = in_schema || '.' || in_table;
+
IF _verbose THEN bVerbose = True; END IF;
-
+
arglen := array_length($4, 1);
IF arglen IS NULL THEN
-- nothing to do, so assume defaults
@@ -109,26 +154,31 @@ export const CREATE_PG_GET_TABLEDEF_SQL = minify(
ELSEIF avarg = 'INCLUDE_TRIGGERS' OR avarg = 'NO_TRIGGERS' THEN
trigcnt = trigcnt + 1;
trigtype = avarg;
+ ELSEIF avarg = 'PKEY_INTERNAL' OR avarg = 'PKEY_EXTERNAL' THEN
+ pkcnt = pkcnt + 1;
+ pktype = avarg;
END IF;
END LOOP;
- IF fkcnt > 1 THEN
- RAISE WARNING 'Only one foreign key option can be provided. You provided %', fkcnt;
- RETURN '';
- ELSEIF trigcnt > 1 THEN
+ IF fkcnt > 1 THEN
+ RAISE WARNING 'Only one foreign key option can be provided. You provided %', fkcnt;
+ RETURN '';
+ ELSEIF trigcnt > 1 THEN
RAISE WARNING 'Only one trigger option can be provided. You provided %', trigcnt;
RETURN '';
- END IF;
+ ELSEIF pkcnt > 1 THEN
+ RAISE WARNING 'Only one pkey option can be provided. You provided %', pkcnt;
+ RETURN '';
+ END IF;
END IF;
-
+
SELECT c.oid, (select setting from pg_settings where name = 'server_version_num') INTO v_table_oid, v_pgversion FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind in ('r','p') AND c.relname = in_table AND n.nspname = in_schema;
- IF bVerbose THEN RAISE NOTICE 'version=%', v_pgversion; END IF;
-
+
-- throw an error if table was not found
IF (v_table_oid IS NULL) THEN
RAISE EXCEPTION 'table does not exist';
END IF;
-
+
-- get user-defined tablespaces if applicable
SELECT tablespace INTO v_temp FROM pg_tables WHERE schemaname = in_schema and tablename = in_table and tablespace IS NOT NULL;
IF v_temp IS NULL THEN
@@ -136,16 +186,16 @@ export const CREATE_PG_GET_TABLEDEF_SQL = minify(
ELSE
v_tablespace := 'TABLESPACE ' || v_temp;
END IF;
-
+
-- also see if there are any SET commands for this table, ie, autovacuum_enabled=off, fillfactor=70
- WITH relopts AS (SELECT unnest(c.reloptions) relopts FROM pg_class c, pg_namespace n WHERE n.nspname = in_schema and n.oid = c.relnamespace and c.relname = in_table)
+ WITH relopts AS (SELECT unnest(c.reloptions) relopts FROM pg_class c, pg_namespace n WHERE n.nspname = in_schema and n.oid = c.relnamespace and c.relname = in_table)
SELECT string_agg(r.relopts, ', ') as relopts INTO v_temp from relopts r;
IF v_temp IS NULL THEN
v_relopts := '';
ELSE
v_relopts := ' WITH (' || v_temp || ')';
END IF;
-
+
-- -----------------------------------------------------------------------------------
-- Create table defs for partitions/children using inheritance or declarative methods.
-- inheritance: pg_class.relkind = 'r' pg_class.relispartition=false pg_class.relpartbound is NULL
@@ -155,14 +205,16 @@ export const CREATE_PG_GET_TABLEDEF_SQL = minify(
bPartition := False;
bInheritance := False;
IF v_pgversion < 100000 THEN
- SELECT c2.relname parent INTO v_parent from pg_class c1, pg_namespace n, pg_inherits i, pg_class c2
- WHERE n.nspname = in_schema and n.oid = c1.relnamespace and c1.relname = in_table and c1.oid = i.inhrelid and i.inhparent = c2.oid and c1.relkind = 'r';
+ -- Issue#11: handle parent schema
+ SELECT c2.relname parent, c2.relnamespace::regnamespace INTO v_parent, v_parent_schema from pg_class c1, pg_namespace n, pg_inherits i, pg_class c2
+ WHERE n.nspname = in_schema and n.oid = c1.relnamespace and c1.relname = in_table and c1.oid = i.inhrelid and i.inhparent = c2.oid and c1.relkind = 'r';
IF (v_parent IS NOT NULL) THEN
bPartition := True;
bInheritance := True;
END IF;
ELSE
- SELECT c2.relname parent, c1.relispartition, pg_get_expr(c1.relpartbound, c1.oid, true) INTO v_parent, bRelispartition, v_partbound from pg_class c1, pg_namespace n, pg_inherits i, pg_class c2
+ -- Issue#11: handle parent schema
+ SELECT c2.relname parent, c1.relispartition, pg_get_expr(c1.relpartbound, c1.oid, true), c2.relnamespace::regnamespace INTO v_parent, bRelispartition, v_partbound, v_parent_schema from pg_class c1, pg_namespace n, pg_inherits i, pg_class c2
WHERE n.nspname = in_schema and n.oid = c1.relnamespace and c1.relname = in_table and c1.oid = i.inhrelid and i.inhparent = c2.oid and c1.relkind = 'r';
IF (v_parent IS NOT NULL) THEN
bPartition := True;
@@ -173,7 +225,6 @@ export const CREATE_PG_GET_TABLEDEF_SQL = minify(
END IF;
END IF;
END IF;
-
IF bPartition THEN
IF bInheritance THEN
-- inheritance-based
@@ -190,7 +241,7 @@ export const CREATE_PG_GET_TABLEDEF_SQL = minify(
END IF;
END IF;
IF bVerbose THEN RAISE INFO '(1)tabledef so far: %', v_table_ddl; END IF;
-
+
IF NOT bPartition THEN
-- see if this is unlogged or temporary table
select c.relpersistence into v_persist from pg_class c, pg_namespace n where n.nspname = in_schema and n.oid = c.relnamespace and c.relname = in_table and c.relkind = 'r';
@@ -202,169 +253,241 @@ export const CREATE_PG_GET_TABLEDEF_SQL = minify(
v_temp := '';
END IF;
END IF;
-
+
-- start the create definition for regular tables unless we are in progress creating an inheritance-based child table
IF NOT bPartition THEN
v_table_ddl := 'CREATE ' || v_temp || ' TABLE ' || in_schema || '.' || in_table || ' (' || E'\\n';
END IF;
- -- RAISE INFO 'DEBUG2: tabledef so far: %', v_table_ddl;
+ -- RAISE INFO 'DEBUG2: tabledef so far: %', v_table_ddl;
-- define all of the columns in the table unless we are in progress creating an inheritance-based child table
IF NOT bPartition THEN
FOR v_colrec IN
- SELECT c.column_name, c.data_type, c.udt_name, c.character_maximum_length, c.is_nullable, c.column_default, c.numeric_precision, c.numeric_scale, c.is_identity, c.identity_generation
+ SELECT c.column_name, c.data_type, c.udt_name, c.character_maximum_length, c.is_nullable, c.column_default, c.numeric_precision, c.numeric_scale, c.is_identity, c.identity_generation
FROM information_schema.columns c WHERE (table_schema, table_name) = (in_schema, in_table) ORDER BY ordinal_position
LOOP
+ SELECT CASE WHEN pg_get_serial_sequence(v_qualified, v_colrec.column_name) IS NOT NULL THEN True ELSE False END into bSerial;
+ IF bVerbose THEN
+ SELECT pg_get_serial_sequence(v_qualified, v_colrec.column_name) into v_temp;
+ IF v_temp IS NULL THEN v_temp = 'NA'; END IF;
+ SELECT pg_temp.pg_get_coldef(in_schema, in_table,v_colrec.column_name) INTO v_diag1;
+ -- RAISE NOTICE 'DEBUG table: % Column: % datatype: % Serial=% serialval=% coldef=%', v_qualified, v_colrec.column_name, v_colrec.data_type, bSerial, v_temp, v_diag1;
+ -- RAISE NOTICE 'DEBUG tabledef: %', v_table_ddl;
+ END IF;
+
v_table_ddl := v_table_ddl || ' ' -- note: two char spacer to start, to indent the column
- || v_colrec.column_name || ' '
- -- || CASE WHEN v_colrec.data_type = 'USER-DEFINED' THEN in_schema || '.' || v_colrec.udt_name ELSE v_colrec.data_type END
- || CASE WHEN v_colrec.udt_name in ('geometry', 'box2d', 'box2df', 'box3d', 'geography', 'geometry_dump', 'gidx', 'spheroid', 'valid_detail')
- THEN v_colrec.udt_name WHEN v_colrec.data_type = 'USER-DEFINED' THEN in_schema || '.' || v_colrec.udt_name ELSE v_colrec.data_type END
- || CASE WHEN v_colrec.is_identity = 'YES' THEN CASE WHEN v_colrec.identity_generation = 'ALWAYS' THEN ' GENERATED ALWAYS AS IDENTITY' ELSE ' GENERATED BY DEFAULT AS IDENTITY' END ELSE '' END
- || CASE WHEN v_colrec.character_maximum_length IS NOT NULL THEN ('(' || v_colrec.character_maximum_length || ')')
- WHEN v_colrec.numeric_precision > 0 AND v_colrec.numeric_scale > 0 THEN '(' || v_colrec.numeric_precision || ',' || v_colrec.numeric_scale || ')'
- ELSE '' END || ' '
- || CASE WHEN v_colrec.is_nullable = 'NO' THEN 'NOT NULL' ELSE 'NULL' END
- || CASE WHEN v_colrec.column_default IS NOT null THEN (' DEFAULT ' || v_colrec.column_default) ELSE '' END
- || ',' || E'\n';
+ || v_colrec.column_name || ' ' ||
+ CASE WHEN v_colrec.udt_name in ('geometry', 'box2d', 'box2df', 'box3d', 'geography', 'geometry_dump', 'gidx', 'spheroid', 'valid_detail')
+ THEN v_colrec.udt_name WHEN v_colrec.data_type = 'USER-DEFINED' THEN in_schema || '.' || v_colrec.udt_name
+ -- Issue#6 fix: handle arrays
+ WHEN v_colrec.data_type = 'ARRAY' THEN pg_temp.pg_get_coldef(in_schema, in_table,v_colrec.column_name)
+ -- Issue#8 fix: handle serial. Note: NOT NULL is implied so no need to declare it explicitly
+ WHEN pg_get_serial_sequence(v_qualified, v_colrec.column_name) IS NOT NULL THEN pg_temp.pg_get_coldef(in_schema, in_table,v_colrec.column_name)
+ ELSE v_colrec.data_type END
+ || CASE WHEN v_colrec.is_identity = 'YES' THEN CASE WHEN v_colrec.identity_generation = 'ALWAYS' THEN ' GENERATED ALWAYS AS IDENTITY' ELSE ' GENERATED BY DEFAULT AS IDENTITY' END ELSE '' END
+ || CASE WHEN v_colrec.character_maximum_length IS NOT NULL THEN ('(' || v_colrec.character_maximum_length || ')')
+ WHEN v_colrec.numeric_precision > 0 AND v_colrec.numeric_scale > 0 THEN '(' || v_colrec.numeric_precision || ',' || v_colrec.numeric_scale || ')'
+ ELSE '' END || ' '
+ || CASE WHEN bSerial THEN '' ELSE CASE WHEN v_colrec.is_nullable = 'NO' THEN 'NOT NULL' ELSE 'NULL' END END
+ || CASE WHEN bSerial THEN '' ELSE CASE WHEN v_colrec.column_default IS NOT null THEN (' DEFAULT ' || v_colrec.column_default) ELSE '' END END
+ || ',' || E'\\n';
END LOOP;
END IF;
IF bVerbose THEN RAISE INFO '(2)tabledef so far: %', v_table_ddl; END IF;
-
- -- define all the constraints
- FOR v_constraintrec IN
- SELECT con.conname as constraint_name, con.contype as constraint_type,
- CASE
- WHEN con.contype = 'p' THEN 1 -- primary key constraint
- WHEN con.contype = 'u' THEN 2 -- unique constraint
- WHEN con.contype = 'f' THEN 3 -- foreign key constraint
- WHEN con.contype = 'c' THEN 4
- ELSE 5
- END as type_rank,
- pg_get_constraintdef(con.oid) as constraint_definition
- FROM pg_catalog.pg_constraint con JOIN pg_catalog.pg_class rel ON rel.oid = con.conrelid JOIN pg_catalog.pg_namespace nsp ON nsp.oid = connamespace
- WHERE nsp.nspname = in_schema AND rel.relname = in_table ORDER BY type_rank
- LOOP
- IF v_constraintrec.type_rank = 1 THEN
- v_primary := True;
- v_constraint_name := v_constraintrec.constraint_name;
- IF bPartition THEN
- continue;
+
+ -- define all the constraints: conparentid does not exist pre PGv11
+ IF v_pgversion < 110000 THEN
+ FOR v_constraintrec IN
+ SELECT con.conname as constraint_name, con.contype as constraint_type,
+ CASE
+ WHEN con.contype = 'p' THEN 1 -- primary key constraint
+ WHEN con.contype = 'u' THEN 2 -- unique constraint
+ WHEN con.contype = 'f' THEN 3 -- foreign key constraint
+ WHEN con.contype = 'c' THEN 4
+ ELSE 5
+ END as type_rank,
+ pg_get_constraintdef(con.oid) as constraint_definition
+ FROM pg_catalog.pg_constraint con JOIN pg_catalog.pg_class rel ON rel.oid = con.conrelid JOIN pg_catalog.pg_namespace nsp ON nsp.oid = connamespace
+ WHERE nsp.nspname = in_schema AND rel.relname = in_table ORDER BY type_rank
+ LOOP
+ IF v_constraintrec.type_rank = 1 THEN
+ v_primary := True;
+ v_constraint_name := v_constraintrec.constraint_name;
+ IF bPartition THEN
+ continue;
END IF;
- END IF;
- -- RAISE INFO 'DEBUG4: constraint name= %', v_constraintrec.constraint_name;
- constraintarr := constraintarr || v_constraintrec.constraint_name;
-
- IF fktype <> 'FKEYS_INTERNAL' AND v_constraintrec.constraint_type = 'f' THEN
- continue;
- END IF;
-
- v_table_ddl := v_table_ddl || ' ' -- note: two char spacer to start, to indent the column
- || 'CONSTRAINT' || ' '
- || v_constraintrec.constraint_name || ' '
- || v_constraintrec.constraint_definition
- || ',' || E'\\n';
- END LOOP;
+ END IF;
+ -- if bVerbose THEN RAISE INFO 'DEBUG4: constraint name= %', v_constraintrec.constraint_name; END IF;
+ constraintarr := constraintarr || v_constraintrec.constraint_name:: text;
+
+ IF fktype <> 'FKEYS_INTERNAL' AND v_constraintrec.constraint_type = 'f' THEN
+ continue;
+ END IF;
+
+ v_table_ddl := v_table_ddl || ' ' -- note: two char spacer to start, to indent the column
+ || 'CONSTRAINT' || ' '
+ || v_constraintrec.constraint_name || ' '
+ || v_constraintrec.constraint_definition
+ || ',' || E'\\n';
+ END LOOP;
+
+ ELSE
+ FOR v_constraintrec IN
+ SELECT con.conname as constraint_name, con.contype as constraint_type,
+ CASE
+ WHEN con.contype = 'p' THEN 1 -- primary key constraint
+ WHEN con.contype = 'u' THEN 2 -- unique constraint
+ WHEN con.contype = 'f' THEN 3 -- foreign key constraint
+ WHEN con.contype = 'c' THEN 4
+ ELSE 5
+ END as type_rank,
+ pg_get_constraintdef(con.oid) as constraint_definition
+ FROM pg_catalog.pg_constraint con JOIN pg_catalog.pg_class rel ON rel.oid = con.conrelid JOIN pg_catalog.pg_namespace nsp ON nsp.oid = connamespace
+ WHERE nsp.nspname = in_schema AND rel.relname = in_table
+ --Issue#13 added this condition:
+ AND con.conparentid = 0
+ ORDER BY type_rank
+ LOOP
+ IF v_constraintrec.type_rank = 1 THEN
+ v_primary := True;
+ v_constraint_name := v_constraintrec.constraint_name;
+ IF bPartition THEN
+ continue;
+ END IF;
+ END IF;
+ -- if bVerbose THEN RAISE INFO 'DEBUG4: constraint name= %', v_constraintrec.constraint_name; END IF;
+ constraintarr := constraintarr || v_constraintrec.constraint_name:: text;
+
+ IF fktype <> 'FKEYS_INTERNAL' AND v_constraintrec.constraint_type = 'f' THEN
+ continue;
+ END IF;
+
+ v_table_ddl := v_table_ddl || ' ' -- note: two char spacer to start, to indent the column
+ || 'CONSTRAINT' || ' '
+ || v_constraintrec.constraint_name || ' '
+ || v_constraintrec.constraint_definition
+ || ',' || E'\\n';
+ END LOOP;
+ END IF;
IF bVerbose THEN RAISE INFO '(3)tabledef so far: %', v_table_ddl; END IF;
-
+
-- drop the last comma before ending the create statement
v_table_ddl = substr(v_table_ddl, 0, length(v_table_ddl) - 1) || E'\\n';
-
+
-- ---------------------------------------------------------------------------
-- at this point we have everything up to the last table-enclosing parenthesis
-- ---------------------------------------------------------------------------
IF bVerbose THEN RAISE INFO '(4)tabledef so far: %', v_table_ddl; END IF;
-
+
-- See if this is an inheritance-based child table and finish up the table create.
IF bPartition and bInheritance THEN
- v_table_ddl := v_table_ddl || ') INHERITS (' || in_schema || '.' || v_parent || ') ' || E'\\n' || v_relopts || ' ' || v_tablespace || ';' || E'\\n';
+ -- Issue#11: handle parent schema
+ -- v_table_ddl := v_table_ddl || ') INHERITS (' || in_schema || '.' || v_parent || ') ' || E'\\n' || v_relopts || ' ' || v_tablespace || ';' || E'\\n';
+ IF v_parent_schema = '' OR v_parent_schema IS NULL THEN v_parent_schema = in_schema; END IF;
+ v_table_ddl := v_table_ddl || ') INHERITS (' || v_parent_schema || '.' || v_parent || ') ' || E'\\n' || v_relopts || ' ' || v_tablespace || ';' || E'\\n';
END IF;
-
+
IF v_pgversion >= 100000 AND NOT bPartition and NOT bInheritance THEN
- -- See if this is a partitioned table (pg_class.relkind = 'p') and add the partitioned key
- SELECT pg_get_partkeydef(c1.oid) as partition_key INTO v_partition_key FROM pg_class c1 JOIN pg_namespace n ON (n.oid = c1.relnamespace) LEFT JOIN pg_partitioned_table p ON (c1.oid = p.partrelid)
+ -- See if this is a partitioned table (pg_class.relkind = 'p') and add the partitioned key
+ SELECT pg_get_partkeydef(c1.oid) as partition_key INTO v_partition_key FROM pg_class c1 JOIN pg_namespace n ON (n.oid = c1.relnamespace) LEFT JOIN pg_partitioned_table p ON (c1.oid = p.partrelid)
WHERE n.nspname = in_schema and n.oid = c1.relnamespace and c1.relname = in_table and c1.relkind = 'p';
-
+
IF v_partition_key IS NOT NULL AND v_partition_key <> '' THEN
-- add partition clause
-- NOTE: cannot specify default tablespace for partitioned relations
- -- v_table_ddl := v_table_ddl || ') PARTITION BY ' || v_partition_key || ' ' || v_tablespace || ';' || E'\\n';
- v_table_ddl := v_table_ddl || ') PARTITION BY ' || v_partition_key || ';' || E'\\n';
+ -- v_table_ddl := v_table_ddl || ') PARTITION BY ' || v_partition_key || ' ' || v_tablespace || ';' || E'\\n';
+ v_table_ddl := v_table_ddl || ') PARTITION BY ' || v_partition_key || ';' || E'\\n';
ELSEIF v_relopts <> '' THEN
- v_table_ddl := v_table_ddl || ') ' || v_relopts || ' ' || v_tablespace || ';' || E'\\n';
+ v_table_ddl := v_table_ddl || ') ' || v_relopts || ' ' || v_tablespace || ';' || E'\\n';
ELSE
-- end the create definition
- v_table_ddl := v_table_ddl || ') ' || v_tablespace || ';' || E'\\n';
- END IF;
+ v_table_ddl := v_table_ddl || ') ' || v_tablespace || ';' || E'\\n';
+ END IF;
END IF;
-
+
IF bVerbose THEN RAISE INFO '(5)tabledef so far: %', v_table_ddl; END IF;
-
+
-- Add closing paren for regular tables
-- IF NOT bPartition THEN
- -- v_table_ddl := v_table_ddl || ') ' || v_relopts || ' ' || v_tablespace || E';\\n';
+ -- v_table_ddl := v_table_ddl || ') ' || v_relopts || ' ' || v_tablespace || E';\\n';
-- END IF;
-- RAISE NOTICE 'ddlsofar3: %', v_table_ddl;
-
+
-- create indexes
FOR v_indexrec IN
SELECT indexdef, COALESCE(tablespace, 'pg_default') as tablespace, indexname FROM pg_indexes WHERE (schemaname, tablename) = (in_schema, in_table)
LOOP
- -- RAISE INFO 'DEBUG6: indexname=%', v_indexrec.indexname;
+ -- RAISE INFO 'DEBUG6: indexname=%', v_indexrec.indexname;
-- loop through constraints and skip ones already defined
bSkip = False;
FOREACH constraintelement IN ARRAY constraintarr
- LOOP
+ LOOP
IF constraintelement = v_indexrec.indexname THEN
-- RAISE INFO 'DEBUG7: skipping index, %', v_indexrec.indexname;
bSkip = True;
EXIT;
END IF;
- END LOOP;
+ END LOOP;
if bSkip THEN CONTINUE; END IF;
-
+
-- Add IF NOT EXISTS clause so partition index additions will not be created if declarative partition in effect and index already created on parent
v_indexrec.indexdef := REPLACE(v_indexrec.indexdef, 'CREATE INDEX', 'CREATE INDEX IF NOT EXISTS');
-- RAISE INFO 'DEBUG8: adding index, %', v_indexrec.indexname;
-
+
-- NOTE: cannot specify default tablespace for partitioned relations
IF v_partition_key IS NOT NULL AND v_partition_key <> '' THEN
v_table_ddl := v_table_ddl || v_indexrec.indexdef || ';' || E'\\n';
ELSE
v_table_ddl := v_table_ddl || v_indexrec.indexdef || ' TABLESPACE ' || v_indexrec.tablespace || ';' || E'\\n';
END IF;
-
+
END LOOP;
IF bVerbose THEN RAISE INFO '(6)tabledef so far: %', v_table_ddl; END IF;
-
- -- Handle external foreign key defs here if applicable.
+
+ -- Handle external foreign key defs here if applicable.
IF fktype = 'FKEYS_EXTERNAL' THEN
- SELECT 'ALTER TABLE ONLY ' || n.nspname || '.' || c2.relname || ' ADD CONSTRAINT ' || r.conname || ' ' || pg_catalog.pg_get_constraintdef(r.oid, true) || ';' into v_fkey_defs
- FROM pg_constraint r, pg_class c1, pg_namespace n, pg_class c2 where r.conrelid = c1.oid and r.contype = 'f' and n.nspname = in_schema and n.oid = r.connamespace and r.conrelid = c2.oid and c2.relname = in_table;
- IF v_fkey_defs IS NOT NULL THEN
- v_table_ddl := v_table_ddl || v_fkey_defs;
- END IF;
- ELSIF fktype = 'FKEYS_COMMENTED' THEN
- SELECT '-- ALTER TABLE ONLY ' || n.nspname || '.' || c2.relname || ' ADD CONSTRAINT ' || r.conname || ' ' || pg_catalog.pg_get_constraintdef(r.oid, true) || ';' into v_fkey_defs
+ -- Issue#13 fix here too for conparentid = 0. and had to change to a loop to handle multiple return set, not a select into variable syntax.
+ -- Also had to account for PG V10 where there is no conparentid
+ IF v_pgversion < 110000 THEN
+ FOR v_constraintrec IN
+ SELECT 'ALTER TABLE ONLY ' || n.nspname || '.' || c2.relname || ' ADD CONSTRAINT ' || r.conname || ' ' || pg_catalog.pg_get_constraintdef(r.oid, true) || ';' as fkeydef
+ FROM pg_constraint r, pg_class c1, pg_namespace n, pg_class c2 where r.conrelid = c1.oid and r.contype = 'f' and n.nspname = in_schema and n.oid = r.connamespace and r.conrelid = c2.oid and c2.relname = in_table
+ LOOP
+ v_table_ddl := v_table_ddl || v_constraintrec.fkeydef || ';' || E'\\n';
+ IF bVerbose THEN RAISE INFO 'keydef = %', v_constraintrec.fkeydef; END IF;
+ END LOOP;
+ ELSE
+ FOR v_constraintrec IN
+ SELECT 'ALTER TABLE ONLY ' || n.nspname || '.' || c2.relname || ' ADD CONSTRAINT ' || r.conname || ' ' || pg_catalog.pg_get_constraintdef(r.oid, true) || ';' as fkeydef
+ FROM pg_constraint r, pg_class c1, pg_namespace n, pg_class c2 where r.conrelid = c1.oid and r.contype = 'f' and n.nspname = in_schema and n.oid = r.connamespace and r.conrelid = c2.oid and c2.relname = in_table and r.conparentid = 0
+ LOOP
+ v_table_ddl := v_table_ddl || v_constraintrec.fkeydef || E'\\n';
+ IF bVerbose THEN RAISE INFO 'keydef = %', v_constraintrec.fkeydef; END IF;
+ END LOOP;
+ END IF;
+
+ ELSIF fktype = 'FKEYS_COMMENTED' THEN
+ SELECT '-- ALTER TABLE ONLY ' || n.nspname || '.' || c2.relname || ' ADD CONSTRAINT ' || r.conname || ' ' || pg_catalog.pg_get_constraintdef(r.oid, true) || ';' into v_fkey_defs
FROM pg_constraint r, pg_class c1, pg_namespace n, pg_class c2 where r.conrelid = c1.oid and r.contype = 'f' and n.nspname = in_schema and n.oid = r.connamespace and r.conrelid = c2.oid and c2.relname = in_table;
IF v_fkey_defs IS NOT NULL THEN
v_table_ddl := v_table_ddl || v_fkey_defs;
END IF;
END IF;
IF bVerbose THEN RAISE INFO '(7)tabledef so far: %', v_table_ddl; END IF;
-
+
IF trigtype = 'INCLUDE_TRIGGERS' THEN
- select pg_get_triggerdef(t.oid, True) || ';' INTO v_trigger FROM pg_trigger t, pg_class c, pg_namespace n
- WHERE n.nspname = in_schema and n.oid = c.relnamespace and c.relname = in_table and c.relkind = 'r' and t.tgrelid = c.oid and NOT t.tgisinternal;
- IF v_trigger <> '' THEN
- v_table_ddl := v_table_ddl || v_trigger;
- END IF;
+ -- Issue#14: handle multiple triggers for a table
+ FOR v_trigrec IN
+ select pg_get_triggerdef(t.oid, True) || ';' as triggerdef FROM pg_trigger t, pg_class c, pg_namespace n
+ WHERE n.nspname = in_schema and n.oid = c.relnamespace and c.relname = in_table and c.relkind = 'r' and t.tgrelid = c.oid and NOT t.tgisinternal
+ LOOP
+ v_table_ddl := v_table_ddl || v_trigrec.triggerdef;
+ v_table_ddl := v_table_ddl || E'\\n';
+ IF bVerbose THEN RAISE INFO 'triggerdef = %', v_trigrec.triggerdef; END IF;
+ END LOOP;
END IF;
-
- -- add empty line
- v_table_ddl := v_table_ddl || E'\\n';
-
+
RETURN v_table_ddl;
-
+
EXCEPTION
WHEN others THEN
BEGIN
@@ -375,7 +498,7 @@ export const CREATE_PG_GET_TABLEDEF_SQL = minify(
-- put additional coding here if necessarY
RETURN '';
END;
-
+
END;
$$;`,
{ compress: true, removeAll: true }
diff --git a/studio/lib/gotrue.ts b/studio/lib/gotrue.ts
index 8fb024e9dba..9a285edf300 100644
--- a/studio/lib/gotrue.ts
+++ b/studio/lib/gotrue.ts
@@ -48,5 +48,17 @@ export const getReturnToPath = (fallback = '/projects') => {
const remainingSearchParams = searchParams.toString()
- return returnTo + (remainingSearchParams ? `?${remainingSearchParams}` : '')
+ let validReturnTo
+
+ // only allow returning to internal pages. e.g. /dashboard
+ try {
+ // if returnTo is a relative path, this will throw an error
+ new URL(returnTo)
+ // if no error, returnTo is a valid URL and NOT an internal page
+ validReturnTo = fallback
+ } catch (_) {
+ validReturnTo = returnTo
+ }
+
+ return validReturnTo + (remainingSearchParams ? `?${remainingSearchParams}` : '')
}
diff --git a/studio/package.json b/studio/package.json
index 6a5bd8834b1..bbef35d8010 100644
--- a/studio/package.json
+++ b/studio/package.json
@@ -17,6 +17,7 @@
"codegen": "openapi-typescript http://localhost:8080/api-json -o ./data/api.d.ts"
},
"dependencies": {
+ "@dagrejs/dagre": "^1.0.2",
"@graphiql/react": "^0.15.0",
"@graphiql/toolkit": "^0.8.0",
"@hcaptcha/react-hcaptcha": "^1.4.4",
@@ -91,6 +92,7 @@
"react-virtualized-auto-sizer": "^1.0.6",
"react-window": "^1.8.6",
"react-window-infinite-loader": "^1.0.7",
+ "reactflow": "^11.7.4",
"recharts": "^2.1.15",
"sass": "^1.55.0",
"scheduler": "^0.20.2",
diff --git a/studio/pages/authorize.tsx b/studio/pages/authorize.tsx
index e0cd2f02f89..a6b134030f6 100644
--- a/studio/pages/authorize.tsx
+++ b/studio/pages/authorize.tsx
@@ -26,7 +26,7 @@ const APIAuthorizationPage: NextPageWithLayout = () => {
const { data: organizations, isLoading: isLoadingOrganizations } = useOrganizationsQuery()
const { data: requester, isLoading, isError, error } = useApiAuthorizationQuery({ id: auth_id })
- const isApproved = requester?.approved_at !== null
+ const isApproved = (requester?.approved_at ?? null) !== null
const isExpired = dayjs().isAfter(dayjs(requester?.expires_at))
const { mutate: approveRequest, isLoading: isApproving } = useApiAuthorizationApproveMutation({
diff --git a/studio/pages/integrations/vercel/[slug]/marketplace/choose-project.tsx b/studio/pages/integrations/vercel/[slug]/marketplace/choose-project.tsx
index bbb7dbd0b86..fa67ca265bb 100644
--- a/studio/pages/integrations/vercel/[slug]/marketplace/choose-project.tsx
+++ b/studio/pages/integrations/vercel/[slug]/marketplace/choose-project.tsx
@@ -4,7 +4,7 @@ import { useParams } from 'common'
import ProjectLinker from 'components/interfaces/Integrations/ProjectLinker'
import { Markdown } from 'components/interfaces/Markdown'
import IntegrationWindowLayout from 'components/layouts/IntegrationWindowLayout'
-import { ScaffoldContainer, ScaffoldDivider } from 'components/layouts/Scaffold'
+import { ScaffoldColumn, ScaffoldContainer, ScaffoldDivider } from 'components/layouts/Scaffold'
import { useOrgIntegrationsQuery } from 'data/integrations/integrations-query-org-only'
import { useVercelProjectsQuery } from 'data/integrations/integrations-vercel-projects-query'
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
@@ -35,9 +35,11 @@ const VercelIntegration: NextPageWithLayout = () => {
const integration = integrationData?.find((x) => x.metadata?.configuration_id === configurationId)
- const { data: supabaseProjectsData } = useProjectsQuery({
- enabled: integration?.id !== undefined,
- })
+ const { data: supabaseProjectsData, isLoading: isLoadingSupabaseProjectsData } = useProjectsQuery(
+ {
+ enabled: integration?.id !== undefined,
+ }
+ )
const supabaseProjects = useMemo(
() =>
@@ -48,45 +50,50 @@ const VercelIntegration: NextPageWithLayout = () => {
[organizationSlug, supabaseProjectsData]
)
- const { data: vercelProjectsData } = useVercelProjectsQuery(
- {
- organization_integration_id: integration?.id,
- },
- { enabled: integration?.id !== undefined }
- )
+ const { data: vercelProjectsData, isLoading: isLoadingVercelProjectsData } =
+ useVercelProjectsQuery(
+ {
+ organization_integration_id: integration?.id,
+ },
+ { enabled: integration?.id !== undefined }
+ )
const vercelProjects = useMemo(() => vercelProjectsData ?? EMPTY_ARR, [vercelProjectsData])
return (
<>
+
-
<>
-
-
+ <>
+
+ {
+ const org = organizationsWithInstalledData?.find((org) => org.slug === e)
+
+ if (org) {
+ setSelectedOrg(org)
+ router.query.organizationSlug = org.slug
+ }
+ }}
+ selectedOrg={selectedOrg}
+ dataLoading={dataLoading}
+ />
+ {disableInstallationForm && (
+
+
+
+ Vercel Integration is already installed.
+
+
+ You will need to choose another organization to install the integration.
+
+
+ )}
+
+
+
+ >
+
-
-
-
+
+
+
+ You can uninstall this Integration at any time.
+
+
+ Remove this integration at any time via Vercel or the Supabase dashboard.
+
+
>
)}
-
+