Skip to content

Commit bda251e

Browse files
authored
Merge pull request #21 from Couchbase-Ecosystem/7-ios-query-api
Add Query API Support
2 parents bc630d5 + d63a841 commit bda251e

File tree

20 files changed

+505
-59
lines changed

20 files changed

+505
-59
lines changed

expo-example/android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,4 @@ allprojects {
3939
maven { url 'https://www.jitpack.io' }
4040
}
4141
}
42-
apply from: "../../android/build.gradle"apply from: "../../android/build.gradle"apply from: "../../android/build.gradle"apply from: "../../android/build.gradle"apply from: "../../android/build.gradle"apply from: "../../android/build.gradle"
42+
apply from: "../../android/build.gradle"

expo-example/app/query/explain.tsx

Whitespace-only changes.
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import React from 'react';
2+
import { Database } from 'cbl-reactnative';
3+
import execute from '@/service/query/execute';
4+
import explain from '@/service/query/explain';
5+
import CBLDatabaseQueryActionContainer from '@/components/CBLDatabaseQueryActionContainer';
6+
7+
export default function QueryParametersScreen() {
8+
function reset() {}
9+
10+
async function runQuery(
11+
database: Database,
12+
sqlQuery: string,
13+
isExplain: boolean
14+
): Promise<string[]> {
15+
try {
16+
const date = new Date().toISOString();
17+
const dict: string[] = [];
18+
if (isExplain) {
19+
const result = await explain(sqlQuery, null, database);
20+
dict.push(`${date}::Explain: <<${result}>>`);
21+
return dict;
22+
} else {
23+
const results = await execute(sqlQuery, null, database);
24+
if (results.length === 0) {
25+
dict.push(`${date}::INFO: No results`);
26+
} else {
27+
for (const result of results) {
28+
dict.push(`${date}::Data: <<${JSON.stringify(result)}>>`);
29+
}
30+
}
31+
return dict;
32+
}
33+
} catch (error) {
34+
// @ts-ignore
35+
return [error.message];
36+
}
37+
}
38+
function updatePressed(
39+
database: Database,
40+
sqlQuery: string
41+
): Promise<string[]> {
42+
return runQuery(database, sqlQuery, false);
43+
}
44+
45+
function explainPressed(
46+
database: Database,
47+
sqlQuery: string
48+
): Promise<string[]> {
49+
return runQuery(database, sqlQuery, true);
50+
}
51+
52+
return (
53+
<CBLDatabaseQueryActionContainer
54+
screenTitle={'Query Parameters'}
55+
handleUpdatePressed={updatePressed}
56+
handleExplainedPressed={explainPressed}
57+
handleResetPressed={reset}
58+
/>
59+
);
60+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import React from 'react';
2+
import { Database } from 'cbl-reactnative';
3+
import execute from '@/service/query/execute';
4+
import explain from '@/service/query/explain';
5+
import CBLDatabaseQueryActionContainer from '@/components/CBLDatabaseQueryActionContainer';
6+
7+
export default function QuerySqlPlusPlusScreen() {
8+
function reset() {}
9+
10+
async function runQuery(
11+
database: Database,
12+
sqlQuery: string,
13+
isExplain: boolean
14+
): Promise<string[]> {
15+
try {
16+
const date = new Date().toISOString();
17+
const dict: string[] = [];
18+
if (isExplain) {
19+
const result = await explain(sqlQuery, null, database);
20+
dict.push(`${date}::Explain: <<${result}>>`);
21+
return dict;
22+
} else {
23+
const results = await execute(sqlQuery, null, database);
24+
if (results.length === 0) {
25+
dict.push(`${date}::INFO: No results`);
26+
} else {
27+
for (const result of results) {
28+
dict.push(`${date}::Data: <<${JSON.stringify(result)}>>`);
29+
}
30+
}
31+
return dict;
32+
}
33+
} catch (error) {
34+
// @ts-ignore
35+
return [error.message];
36+
}
37+
}
38+
39+
function updatePressed(
40+
database: Database,
41+
sqlQuery: string
42+
): Promise<string[]> {
43+
return runQuery(database, sqlQuery, false);
44+
}
45+
46+
function explainPressed(
47+
database: Database,
48+
sqlQuery: string
49+
): Promise<string[]> {
50+
return runQuery(database, sqlQuery, true);
51+
}
52+
53+
return (
54+
<CBLDatabaseQueryActionContainer
55+
screenTitle={'Query Workbench'}
56+
handleUpdatePressed={updatePressed}
57+
handleExplainedPressed={explainPressed}
58+
handleResetPressed={reset}
59+
/>
60+
);
61+
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import React, { useContext, useState } from 'react';
2+
import DatabaseContext from '@/providers/DatabaseContext';
3+
import { useNavigation } from '@react-navigation/native';
4+
import { useStyleScheme } from '@/components/Themed';
5+
import useNavigationBarTitleResetOption from '@/hooks/useNavigationBarTitleResetOption';
6+
import { SafeAreaView } from 'react-native';
7+
import ResultListView from '@/components/ResultsListView';
8+
import DatabaseNameForm from '@/components/DatabaseNameForm';
9+
import { CBLDatabaseQueryActionContainerProps } from '@/types/CBLDatabaseQueryActionContainerProps.type';
10+
import { Database } from 'cbl-reactnative';
11+
import HeaderToolbarView from '@/components/HeaderToolbarView';
12+
import { StyledTextInput } from '@/components/StyledTextInput';
13+
14+
export default function CBLDatabaseQueryActionContainer({
15+
screenTitle,
16+
handleUpdatePressed,
17+
handleExplainedPressed,
18+
handleResetPressed,
19+
children,
20+
}: CBLDatabaseQueryActionContainerProps) {
21+
const { databases } = useContext(DatabaseContext)!;
22+
const [databaseName, setDatabaseName] = useState<string>('');
23+
const [sqlQuery, setSqlQuery] = useState<string>('');
24+
const [resultMessage, setResultsMessage] = useState<string[]>([]);
25+
const navigation = useNavigation();
26+
const styles = useStyleScheme();
27+
useNavigationBarTitleResetOption(screenTitle, navigation, reset);
28+
29+
function isFormValidate(): boolean {
30+
let isValid = true;
31+
if (databaseName === '') {
32+
setResultsMessage((prev) => [
33+
...prev,
34+
'Error: Database name is required',
35+
]);
36+
isValid = false;
37+
} else if (sqlQuery === '') {
38+
setResultsMessage((prev) => [...prev, 'Error: SQL Query is required']);
39+
isValid = false;
40+
}
41+
return isValid;
42+
}
43+
44+
async function update(isExplain: boolean) {
45+
if (isFormValidate()) {
46+
try {
47+
if (
48+
databaseName in databases &&
49+
databases[databaseName] instanceof Database
50+
) {
51+
const database = databases[databaseName];
52+
let resultMessages: string[] = [];
53+
if (isExplain) {
54+
resultMessages = await handleExplainedPressed(database, sqlQuery);
55+
} else {
56+
resultMessages = await handleUpdatePressed(database, sqlQuery);
57+
}
58+
setResultsMessage((prev) => [...prev, ...resultMessages]);
59+
} else {
60+
setResultsMessage((prev) => [
61+
...prev,
62+
`Error: Database <${databaseName}> not found in context. Make sure database was opened first prior to trying to use it.`,
63+
]);
64+
}
65+
} catch (error) {
66+
// @ts-ignore
67+
setResultsMessage((prev) => [...prev, error.message]);
68+
}
69+
}
70+
}
71+
72+
function reset() {
73+
setDatabaseName('');
74+
setSqlQuery('');
75+
setResultsMessage([]);
76+
handleResetPressed();
77+
}
78+
79+
function updatePressed() {
80+
return update(false);
81+
}
82+
83+
function explainPressed() {
84+
return update(true);
85+
}
86+
87+
const icons = [
88+
{
89+
iconName: 'text-box-search',
90+
onPress: explainPressed,
91+
},
92+
{
93+
iconName: 'play',
94+
onPress: updatePressed,
95+
},
96+
];
97+
return (
98+
<SafeAreaView style={styles.container}>
99+
<DatabaseNameForm
100+
setDatabaseName={setDatabaseName}
101+
databaseName={databaseName}
102+
/>
103+
<HeaderToolbarView
104+
name="Query Editor"
105+
iconName="database-search"
106+
icons={icons}
107+
/>
108+
<StyledTextInput
109+
autoCapitalize="none"
110+
style={[
111+
styles.textInput,
112+
{ height: undefined, minHeight: 120, marginTop: 5, marginBottom: 15 },
113+
]}
114+
placeholder="SQL++ Query"
115+
onChangeText={(newText) => setSqlQuery(newText)}
116+
defaultValue={sqlQuery}
117+
multiline={true}
118+
/>
119+
{children && children}
120+
<ResultListView messages={resultMessage} />
121+
</SafeAreaView>
122+
);
123+
}

expo-example/components/ResultsListView.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,27 @@
11
import HeaderView from '@/components/HeaderView';
22
import React from 'react';
3-
import { ScrollView } from 'react-native';
3+
import { ScrollView, View } from 'react-native';
44
import { Text } from '@/components/Themed';
55
import { ResultsListViewProps } from '@/types/resultsListViewProps.type';
6+
import { Divider } from '@gluestack-ui/themed';
67

78
export default function ResultListView({ messages }: ResultsListViewProps) {
9+
const date = new Date().toISOString();
810
return (
911
<>
1012
<HeaderView name={'Results'} iconName={'information'} />
11-
<ScrollView style={{ marginLeft: 16 }}>
13+
<ScrollView>
1214
{messages?.map((message, index) => (
13-
<Text key={`message-${index}`}>{message}</Text>
15+
<View
16+
style={{ marginLeft: 16, marginTop: 12 }}
17+
key={`view-${index}-${date}`}
18+
>
19+
<Text key={`message-${index}-${date}`}>{message}</Text>
20+
<Divider
21+
key={`divider-${index}-${date}`}
22+
style={{ marginTop: 14, marginBottom: 4, marginLeft: 2 }}
23+
/>
24+
</View>
1425
))}
1526
</ScrollView>
1627
</>

expo-example/hooks/useQueryNavigationSections.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export function useQueryNavigationSections() {
99
title: 'SQL++ Query Editor',
1010
path: '/query/sqlPlusPlus',
1111
},
12-
{ id: 2, title: 'Query Explain', path: '/query/explain' },
12+
{ id: 2, title: 'Query Parameters', path: '/query/parameters' },
1313
{ id: 3, title: 'Live Query', path: '/query/live' },
1414
],
1515
},

0 commit comments

Comments
 (0)