Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Import a JSON file into a table. A dedicated sheet accepts an array of objects, newline-delimited JSON, or TablePro's own JSON export, and lets you map each field to a column in an existing table or in a new table with inferred, editable columns.
- The window title bar shows the open table's name, with its database and schema below, so you can tell which table you're viewing without checking the sidebar. (#1475)
- iOS: open DuckDB database files and in-memory DuckDB databases, matching the Mac app. (#1526)
- Save the current query as a favorite from a star button in the SQL editor toolbar.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,22 @@ public protocol ImportFormatPlugin: TableProPlugin {
static var iconName: String { get }
static var supportedDatabaseTypeIds: [String] { get }
static var excludedDatabaseTypeIds: [String] { get }
static var requiresTargetTable: Bool { get }

func performImport(
source: any PluginImportSource,
sink: any PluginImportDataSink,
progress: PluginImportProgress
) async throws -> PluginImportResult

func detectSourceFields(at url: URL, targetTable: String?) throws -> [PluginImportField]
}

public extension ImportFormatPlugin {
static var capabilities: [PluginCapability] { [.importFormat] }
static var supportedDatabaseTypeIds: [String] { [] }
static var excludedDatabaseTypeIds: [String] { [] }
static var requiresTargetTable: Bool { false }

func detectSourceFields(at url: URL, targetTable: String?) throws -> [PluginImportField] { [] }
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import Foundation

public protocol PluginImportDataSink: AnyObject, Sendable {
var databaseTypeId: String { get }
var targetTable: String? { get }
func execute(statement: String) async throws
func insertRow(_ values: [String: PluginCellValue]) async throws
func deleteAllRowsFromTargetTable() async throws
func beginTransaction() async throws
func commitTransaction() async throws
func rollbackTransaction() async throws
Expand All @@ -16,6 +19,13 @@ public protocol PluginImportDataSink: AnyObject, Sendable {
}

public extension PluginImportDataSink {
var targetTable: String? { nil }
func insertRow(_ values: [String: PluginCellValue]) async throws {
throw PluginImportError.importFailed("Row-based import is not supported by this connection")
}
func deleteAllRowsFromTargetTable() async throws {
throw PluginImportError.importFailed("Clearing the target table is not supported by this connection")
}
func disableForeignKeyChecks() async throws {}
func enableForeignKeyChecks() async throws {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,28 @@ public enum ImportErrorHandling: String, Codable, CaseIterable, Sendable {
case skipAndContinue
}

public enum PluginImportFieldType: String, Sendable {
case text
case integer
case real
case boolean
case json
}

public struct PluginImportField: Sendable, Identifiable {
public let name: String
public let sampleValue: String?
public let inferredType: PluginImportFieldType

public var id: String { name }

public init(name: String, sampleValue: String?, inferredType: PluginImportFieldType) {
self.name = name
self.sampleValue = sampleValue
self.inferredType = inferredType
}
}

public struct PluginImportResult: Sendable {
public let executedStatements: Int
public let skippedStatements: Int
Expand Down
12 changes: 12 additions & 0 deletions Plugins/JSONImportPlugin/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>TableProPluginKitVersion</key>
<integer>17</integer>
<key>TableProProvidesImportFormatIds</key>
<array>
<string>json</string>
</array>
</dict>
</plist>
13 changes: 13 additions & 0 deletions Plugins/JSONImportPlugin/JSONImportOptions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// JSONImportOptions.swift
// JSONImportPlugin
//

import Foundation
import TableProPluginKit

struct JSONImportOptions: Equatable, Codable {
var errorHandling: ImportErrorHandling = .stopAndRollback
var wrapInTransaction: Bool = true
var deleteExistingRows: Bool = false
}
34 changes: 34 additions & 0 deletions Plugins/JSONImportPlugin/JSONImportOptionsView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// JSONImportOptionsView.swift
// JSONImportPlugin
//

import SwiftUI
import TableProPluginKit

struct JSONImportOptionsView: View {
let plugin: JSONImportPlugin

var body: some View {
VStack(alignment: .leading, spacing: 12) {
Picker("On error:", selection: Bindable(plugin).settings.errorHandling) {
Text("Stop and Rollback").tag(ImportErrorHandling.stopAndRollback)
Text("Stop and Commit").tag(ImportErrorHandling.stopAndCommit)
Text("Skip and Continue").tag(ImportErrorHandling.skipAndContinue)
}
.pickerStyle(.menu)
.font(.system(size: 13))

Toggle("Wrap in transaction (BEGIN/COMMIT)", isOn: Bindable(plugin).settings.wrapInTransaction)
.font(.system(size: 13))
.disabled(plugin.settings.errorHandling == .skipAndContinue)
.help(plugin.settings.errorHandling == .skipAndContinue
? String(localized: "Not available in skip-and-continue mode")
: String(localized: "Insert all rows in a single transaction. If any row fails, all changes are rolled back."))

Toggle("Delete existing rows before import", isOn: Bindable(plugin).settings.deleteExistingRows)
.font(.system(size: 13))
.help("Remove every row from the target table before inserting the imported rows.")
}
}
}
Loading
Loading