From 176ab4bf70ca14db632620f8ac4f38057c9896d5 Mon Sep 17 00:00:00 2001 From: "Danila Grobov (s4642g)" Date: Sun, 13 Apr 2025 20:53:58 +0300 Subject: [PATCH 01/21] Add changes from file explorer feature. --- client/src/connection/itc/CodeRunner.ts | 52 +++++++++++++------ client/src/connection/itc/index.ts | 31 +++++++++-- client/src/connection/itc/script.ts | 68 +++++++++++++------------ client/src/connection/util.ts | 15 ++++++ 4 files changed, 115 insertions(+), 51 deletions(-) diff --git a/client/src/connection/itc/CodeRunner.ts b/client/src/connection/itc/CodeRunner.ts index 77f4ca6b0..b85ee0066 100644 --- a/client/src/connection/itc/CodeRunner.ts +++ b/client/src/connection/itc/CodeRunner.ts @@ -2,25 +2,55 @@ // SPDX-License-Identifier: Apache-2.0 import { commands } from "vscode"; +import { v4 } from "uuid"; + import { ITCSession } from "."; import { LogLine, getSession } from ".."; import { useRunStore } from "../../store"; +import { Session } from "../session"; +import { extractTextBetweenTags } from "../util"; let wait: Promise | undefined; +export async function executeRawCode(code: string): Promise { + const randomId = v4(); + const startTag = `<${randomId}>`; + const endTag = ``; + const task = () => + _runCode( + async (session) => { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + await (session as ITCSession).execute( + `Write-Host "${startTag}"\n${code}\nWrite-Host "${endTag}"\n`, + ); + }, + startTag, + endTag, + ); + + wait = wait ? wait.then(task) : task(); + return wait; +} + export async function runCode( code: string, startTag: string = "", endTag: string = "", ): Promise { - const task = () => _runCode(code, startTag, endTag); + const task = () => + _runCode( + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + async (session) => await (session as ITCSession).run(code, true), + startTag, + endTag, + ); wait = wait ? wait.then(task) : task(); return wait; } async function _runCode( - code: string, + runCallback: (session: Session) => void, startTag: string = "", endTag: string = "", ): Promise { @@ -45,8 +75,9 @@ async function _runCode( const onExecutionLogFn = session.onExecutionLogFn; const outputLines = []; - const addLine = (logLines: LogLine[]) => + const addLine = (logLines: LogLine[]) => { outputLines.push(...logLines.map(({ line }) => line)); + }; try { await session.setup(true); @@ -54,21 +85,10 @@ async function _runCode( // Lets capture output to use it on session.onExecutionLogFn = addLine; - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - await (session as ITCSession).run(code, true); + await runCallback(session); const logOutput = outputLines.filter((line) => line.trim()).join(""); - - logText = - startTag && endTag - ? logOutput - .slice( - logOutput.lastIndexOf(startTag), - logOutput.lastIndexOf(endTag), - ) - .replace(startTag, "") - .replace(endTag, "") - : logOutput; + logText = extractTextBetweenTags(logOutput, startTag, endTag); } finally { unsubscribe && unsubscribe(); // Lets update our session to write to the log diff --git a/client/src/connection/itc/index.ts b/client/src/connection/itc/index.ts index aca22da5a..b34852cd1 100644 --- a/client/src/connection/itc/index.ts +++ b/client/src/connection/itc/index.ts @@ -27,7 +27,7 @@ import { WORK_DIR_END_TAG, WORK_DIR_START_TAG, } from "./const"; -import { scriptContent } from "./script"; +import { getScript } from "./script"; import { Config, ITCProtocol, LineCodes } from "./types"; import { decodeEntities, escapePowershellString } from "./util"; @@ -110,9 +110,16 @@ export class ITCSession extends Session { ); this._shellProcess.stdout.on("data", this.onShellStdOut); this._shellProcess.stderr.on("data", this.onShellStdErr); - this._shellProcess.stdin.write(scriptContent + "\n", this.onWriteComplete); this._shellProcess.stdin.write( - `$runner = New-Object -TypeName SASRunner -ArgumentList "${escapePowershellString(interopLibraryFolderPath || "")}"\n`, + getScript({ + interopLibraryFolderPath: escapePowershellString( + interopLibraryFolderPath || "", + ), + }) + "\n", + this.onWriteComplete, + ); + this._shellProcess.stdin.write( + `$runner = New-Object -TypeName SASRunner\n`, this.onWriteComplete, ); @@ -246,6 +253,24 @@ export class ITCSession extends Session { return runPromise; }; + public execute = async (code: string): Promise => { + const runPromise = new Promise((resolve, reject) => { + this._runResolve = resolve; + this._runReject = reject; + }); + + this._html5FileName = ""; + this._pollingForLogResults = true; + const codeToExecute = `${code}\nWrite-Host "${LineCodes.RunEndCode}"\n`; + this._shellProcess.stdin.write(codeToExecute, async (error) => { + if (error) { + this._runReject(error); + } + }); + + return runPromise; + }; + /** * Cleans up resources for the given SAS session. * @returns void promise. diff --git a/client/src/connection/itc/script.ts b/client/src/connection/itc/script.ts index 1afea0f9a..f8145f7e5 100644 --- a/client/src/connection/itc/script.ts +++ b/client/src/connection/itc/script.ts @@ -8,45 +8,49 @@ import { } from "./const"; import { LineCodes } from "./types"; -export const scriptContent = ` -class SASRunner{ - [System.__ComObject] $objSAS - - SASRunner([string]$interopPath) { - try { - $interopDir = $this.GetInteropDirectory($interopPath) - Add-Type -Path "$interopDir\\SASInterop.dll" - Add-Type -Path "$interopDir\\SASOManInterop.dll" - } catch { - Write-Error "${ERROR_START_TAG}LoadingInterop error: $_${ERROR_END_TAG}" +type ScriptProperties = { + interopLibraryFolderPath?: string; +}; + +export const getScript = ({ + interopLibraryFolderPath = "", +}: ScriptProperties) => ` +function GetInteropDirectory { + # try to load from user specified path first + if ("${interopLibraryFolderPath}") { + if (Test-Path -Path "${interopLibraryFolderPath}\\SASInterop.dll") { + return "${interopLibraryFolderPath}" } } - [string] GetInteropDirectory([string]$defaultInteropPath) { - # try to load from user specified path first - if ($defaultInteropPath) { - if (Test-Path -Path "$defaultInteropPath\\SASInterop.dll") { - return $defaultInteropPath - } + # try to load path from registry + try { + $pathFromRegistry = (Get-ItemProperty -ErrorAction Stop -Path "HKLM:\\SOFTWARE\\WOW6432Node\\SAS Institute Inc.\\Common Data\\Shared Files\\Integration Technologies").Path + if (Test-Path -Path "$pathFromRegistry\\SASInterop.dll") { + return $pathFromRegistry } + } catch { + } - # try to load path from registry - try { - $pathFromRegistry = (Get-ItemProperty -ErrorAction Stop -Path "HKLM:\\SOFTWARE\\WOW6432Node\\SAS Institute Inc.\\Common Data\\Shared Files\\Integration Technologies").Path - if (Test-Path -Path "$pathFromRegistry\\SASInterop.dll") { - return $pathFromRegistry - } - } catch { - } + # try to load path from integration technologies + $itcPath = "C:\\Program Files\\SASHome\\x86\\Integration Technologies" + if (Test-Path -Path "$itcPath\\SASInterop.dll") { + return $itcPath + } - # try to load path from integration technologies - $itcPath = "C:\\Program Files\\SASHome\\x86\\Integration Technologies" - if (Test-Path -Path "$itcPath\\SASInterop.dll") { - return $itcPath - } + return "" +} - return "" - } +try { + $interopDir = GetInteropDirectory + Add-Type -Path "$interopDir\\SASInterop.dll" + Add-Type -Path "$interopDir\\SASOManInterop.dll" +} catch { + Write-Error "${ERROR_START_TAG}LoadingInterop error: $_${ERROR_END_TAG}" +} + +class SASRunner{ + [System.__ComObject] $objSAS [void]ResolveSystemVars(){ try { diff --git a/client/src/connection/util.ts b/client/src/connection/util.ts index cea544de5..f93db6636 100644 --- a/client/src/connection/util.ts +++ b/client/src/connection/util.ts @@ -9,3 +9,18 @@ export function extractOutputHtmlFileName( line.match(/body="(.{8}-.{4}-.{4}-.{4}-.{12}).htm"/)?.[1] ?? defaultValue ); } + +export const extractTextBetweenTags = ( + text: string, + startTag: string = "", + endTag: string = "", +): string => { + return startTag && endTag + ? text + .slice(text.lastIndexOf(startTag), text.lastIndexOf(endTag)) + .replace(startTag, "") + .replace(endTag, "") + .replace(/^\n/, "") + .replace(/\n$/, "") + : text; +}; From c12016f55f3e4121dd88cfddd896d2ea51b761cc Mon Sep 17 00:00:00 2001 From: "Danila Grobov (s4642g)" Date: Thu, 1 May 2025 22:56:47 +0300 Subject: [PATCH 02/21] Use data connection instead of code execution. --- .../src/connection/itc/ItcLibraryAdapter.ts | 64 ++++++------------- client/src/connection/itc/script.ts | 60 +++++++++++++++++ 2 files changed, 81 insertions(+), 43 deletions(-) diff --git a/client/src/connection/itc/ItcLibraryAdapter.ts b/client/src/connection/itc/ItcLibraryAdapter.ts index 84ffa1847..93f3ddf06 100644 --- a/client/src/connection/itc/ItcLibraryAdapter.ts +++ b/client/src/connection/itc/ItcLibraryAdapter.ts @@ -12,7 +12,7 @@ import { TableRow, } from "../../components/LibraryNavigator/types"; import { Column, ColumnCollection } from "../rest/api/compute"; -import { runCode } from "./CodeRunner"; +import { executeRawCode, runCode } from "./CodeRunner"; import { Config } from "./types"; class ItcLibraryAdapter implements LibraryAdapter { @@ -213,48 +213,16 @@ class ItcLibraryAdapter implements LibraryAdapter { start: number, limit: number, ): Promise<{ rows: Array; count: number }> { - const maxTableNameLength = 32; - const tempTable = `${item.name}${hms()}${start}`.substring( - 0, - maxTableNameLength, - ); + const fullTableName = `${item.library}.${item.name}`; const code = ` - options nonotes nosource nodate nonumber; - %let COUNT; - proc sql; - SELECT COUNT(1) into: COUNT FROM ${item.library}.${item.name}; - quit; - data work.${tempTable}; - set ${item.library}.${item.name}; - if ${start + 1} <= _N_ <= ${start + limit} then output; - run; - - filename out temp; - proc json nokeys out=out pretty; export work.${tempTable}; run; - - %put ; - %put &COUNT; - data _null_; infile out; input; put _infile_; run; - %put ; - proc datasets library=work nolist nodetails; delete ${tempTable}; run; - options notes source date number; + $runner.GetDatasetRecords("${fullTableName}", ${start}, ${limit}) `; - - let output = await this.runCode(code, "", ""); - - // Extract result count - const countRegex = /(.*)<\/Count>/; - const countMatches = output.match(countRegex); - const count = parseInt(countMatches[1].replace(/\s|\n/gm, ""), 10); - output = output.replace(countRegex, ""); - - const rows = output.replace(/\n|\t/gm, "").slice(output.indexOf("{")); + const output = await executeRawCode(code); try { - const tableData = JSON.parse(rows); - return { rows: tableData[`SASTableData+${tempTable}`], count }; + return JSON.parse(output); } catch (e) { console.warn("Failed to load table data with error", e); - console.warn("Raw output", rows); + console.warn("Raw output", output); throw new Error( l10n.t( "An error was encountered when loading table data. This usually happens when a table is too large or the data couldn't be processed. See console for more details.", @@ -263,19 +231,29 @@ class ItcLibraryAdapter implements LibraryAdapter { } } - protected async runCode( - code: string, - startTag: string = "", - endTag: string = "", + protected async executionHandler( + callback: () => Promise, ): Promise { try { - return await runCode(code, startTag, endTag); + return await callback(); } catch (e) { onRunError(e); commands.executeCommand("setContext", "SAS.librariesDisplayed", false); return ""; } } + + protected async runCode( + code: string, + startTag: string = "", + endTag: string = "", + ): Promise { + return this.executionHandler(() => runCode(code, startTag, endTag)); + } + + protected async executeRawCode(code: string): Promise { + return this.executionHandler(() => executeRawCode(code)); + } } const processQueryRows = (response: string): string[] => { diff --git a/client/src/connection/itc/script.ts b/client/src/connection/itc/script.ts index f8145f7e5..1a064bbba 100644 --- a/client/src/connection/itc/script.ts +++ b/client/src/connection/itc/script.ts @@ -15,6 +15,7 @@ type ScriptProperties = { export const getScript = ({ interopLibraryFolderPath = "", }: ScriptProperties) => ` +using namespace System.Collections.Generic function GetInteropDirectory { # try to load from user specified path first if ("${interopLibraryFolderPath}") { @@ -51,6 +52,8 @@ try { class SASRunner{ [System.__ComObject] $objSAS + [System.__ComObject] $objKeeper + [System.__ComObject] $dataConnection [void]ResolveSystemVars(){ try { @@ -89,6 +92,20 @@ class SASRunner{ $password ) + $this.objKeeper = New-Object -ComObject SASObjectManager.ObjectKeeper + $this.objKeeper.AddObject(1, "WorkspaceObject", $this.objSAS) + + $this.dataConnection = New-Object -comobject ADODB.Connection + $this.dataConnection.Provider = "sas.IOMProvider" + $this.dataConnection.Properties("SAS Workspace ID") = $this.objSAS.UniqueIdentifier + $this.dataConnection.Properties("Data Source") = "Data Source Name" + $this.dataConnection.Properties("SAS Port") = $port + $this.dataConnection.Properties("SAS Machine DNS Name") = $profileHost + $this.dataConnection.Properties("SAS Protocol") = $protocol + $this.dataConnection.Properties("User ID") = $username + $this.dataConnection.Properties("Password") = $password + $this.dataConnection.Open() + Write-Host "${LineCodes.SessionCreatedCode}" } catch { Write-Error "${ERROR_START_TAG}Setup error: $_${ERROR_END_TAG}" @@ -151,6 +168,8 @@ class SASRunner{ [void]Close(){ try{ + $this.dataConnection.Close() + $this.objKeeper.RemoveObject($this.objSAS) $this.objSAS.Close() }catch{ Write-Error "${ERROR_START_TAG}Close error: $_${ERROR_END_TAG}" @@ -228,5 +247,46 @@ class SASRunner{ Write-Host "${LineCodes.ResultsFetchedCode}" } + + [void]GetDatasetRecords([string]$tableName, [int]$start = 0, [int]$limit = 100) { + $objRecordSet = New-Object -comobject ADODB.Recordset + $objRecordSet.ActiveConnection = $this.dataConnection # This is needed to set the properties for sas formats. + $objRecordSet.Properties.Item("SAS Formats").Value = "_ALL_" + + $objRecordSet.Open( + $tableName, + [System.Reflection.Missing]::Value, # Use the active connection + 2, # adOpenDynamic + 1, # adLockReadOnly + 512 # adCmdTableDirect + ) + + $records = [List[List[object]]]::new() + $fields = $objRecordSet.Fields.Count + $objRecordSet.AbsolutePosition = $start + 1 + + for ($j = 0; $j -lt $limit -and $objRecordSet.EOF -eq $False; $j++) { + $cell = [List[object]]::new() + for ($i = 0; $i -lt $fields; $i++) { + $cell.Add($objRecordSet.Fields.Item($i).Value) + } + $records.Add($cell) + $objRecordSet.MoveNext() + } + $objRecordSet.Close() + + $objRecordSet.Open( + "SELECT COUNT(1) FROM $tableName", + $this.dataConnection, 3, 1, 1 + ) # adOpenStatic, adLockReadOnly, adCmdText + $count = $objRecordSet.Fields.Item(0).Value + $objRecordSet.Close() + + $result = New-Object psobject + $result | Add-Member -MemberType NoteProperty -Name "rows" -Value $records + $result | Add-Member -MemberType NoteProperty -Name "count" -Value $count + + Write-Host $($result | ConvertTo-Json -Depth 10) + } } `; From 9d1aff6e69633917a7a9098de102142dd0ce102d Mon Sep 17 00:00:00 2001 From: "Danila Grobov (s4642g)" Date: Sat, 3 May 2025 19:02:19 +0300 Subject: [PATCH 03/21] Improve columns loading time. --- .../src/connection/itc/ItcLibraryAdapter.ts | 29 ++------------ client/src/connection/itc/script.ts | 39 +++++++++++++++++++ 2 files changed, 43 insertions(+), 25 deletions(-) diff --git a/client/src/connection/itc/ItcLibraryAdapter.ts b/client/src/connection/itc/ItcLibraryAdapter.ts index 93f3ddf06..ed3a07947 100644 --- a/client/src/connection/itc/ItcLibraryAdapter.ts +++ b/client/src/connection/itc/ItcLibraryAdapter.ts @@ -45,33 +45,12 @@ class ItcLibraryAdapter implements LibraryAdapter { } public async getColumns(item: LibraryItem): Promise { - const sql = ` - %let OUTPUT; - proc sql; - select catx(',', name, type, varnum) as column into: OUTPUT separated by '~' - from sashelp.vcolumn - where libname='${item.library}' and memname='${item.name}' - order by varnum; - quit; - %put &OUTPUT; %put ; + const code = ` + $runner.GetColumns("${item.library}", "${item.name}") `; - - const columnLines = processQueryRows( - await this.runCode(sql, "", ""), - ); - - const columns = columnLines.map((lineText): Column => { - const [name, type, index] = lineText.split(","); - - return { - name, - type, - index: parseInt(index, 10), - }; - }); - + const output = await executeRawCode(code); return { - items: columns, + items: JSON.parse(output), count: -1, }; } diff --git a/client/src/connection/itc/script.ts b/client/src/connection/itc/script.ts index 1a064bbba..ea014d67f 100644 --- a/client/src/connection/itc/script.ts +++ b/client/src/connection/itc/script.ts @@ -263,6 +263,12 @@ class SASRunner{ $records = [List[List[object]]]::new() $fields = $objRecordSet.Fields.Count + + if ($objRecordSet.EOF) { + Write-Host '{"rows": [], "count": 0}' + return + } + $objRecordSet.AbsolutePosition = $start + 1 for ($j = 0; $j -lt $limit -and $objRecordSet.EOF -eq $False; $j++) { @@ -288,5 +294,38 @@ class SASRunner{ Write-Host $($result | ConvertTo-Json -Depth 10) } + + [void]GetColumns([string]$libname, [string]$memname) { + $objRecordSet = New-Object -comobject ADODB.Recordset + $objRecordSet.ActiveConnection = $this.dataConnection + $query = @" + select name, type, format + from sashelp.vcolumn + where libname='$libname' and memname='$memname'; +"@ + $objRecordSet.Open( + $query, + [System.Reflection.Missing]::Value, # Use the active connection + 2, # adOpenDynamic + 1, # adLockReadOnly + 1 # adCmdTableDirect + ) + + $rows = $objRecordSet.GetRows() + + $objRecordSet.Close() + + $parsedRows = @() + for ($i = 0; $i -lt $rows.GetLength(1); $i++) { + $parsedRow = [PSCustomObject]@{ + index = $i + 1 + name = $rows[0, $i] + type = $rows[1, $i] + format = $rows[2, $i] + } + $parsedRows += $parsedRow + } + Write-Host $($parsedRows | ConvertTo-Json -Depth 10) + } } `; From 328b22d928d0def7016e60d0d7ccb4d29999839d Mon Sep 17 00:00:00 2001 From: "Danila Grobov (s4642g)" Date: Sat, 3 May 2025 19:26:22 +0300 Subject: [PATCH 04/21] Add column type mapping to the format. --- .../src/connection/itc/ItcLibraryAdapter.ts | 7 +- client/src/connection/util.ts | 71 +++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/client/src/connection/itc/ItcLibraryAdapter.ts b/client/src/connection/itc/ItcLibraryAdapter.ts index ed3a07947..4d81f9a3d 100644 --- a/client/src/connection/itc/ItcLibraryAdapter.ts +++ b/client/src/connection/itc/ItcLibraryAdapter.ts @@ -12,6 +12,7 @@ import { TableRow, } from "../../components/LibraryNavigator/types"; import { Column, ColumnCollection } from "../rest/api/compute"; +import { getColumnIconType } from "../util"; import { executeRawCode, runCode } from "./CodeRunner"; import { Config } from "./types"; @@ -49,8 +50,12 @@ class ItcLibraryAdapter implements LibraryAdapter { $runner.GetColumns("${item.library}", "${item.name}") `; const output = await executeRawCode(code); + const columns = JSON.parse(output).map((column) => ({ + ...column, + type: getColumnIconType(column), + })); return { - items: JSON.parse(output), + items: columns, count: -1, }; } diff --git a/client/src/connection/util.ts b/client/src/connection/util.ts index f93db6636..1a290cde9 100644 --- a/client/src/connection/util.ts +++ b/client/src/connection/util.ts @@ -24,3 +24,74 @@ export const extractTextBetweenTags = ( .replace(/\n$/, "") : text; }; + +export const getColumnIconType = ({ + type, + format, +}: { + index: number; + type: string; + name: string; + format: string; +}) => { + format = format.toUpperCase(); + + const isDateFormat = () => + [ + "DAT", + "MM", + "DD", + "YY", + "EURDF", + "JUL", + "YEAR", + "DAY", + "MONTH", + "MON", + "DOWNAME", + ].some((f) => format.includes(f)) && + ![ + "TIME", + "HH", + "SS", + "COMM", + "DATEAMPM", + "DATETIME", + "NLDATMTM", + "NLDATM", + "NLDATMAP", + "NLDATMW", + ].some((f) => format.includes(f)); + + const isTimeFormat = () => + ["TIME", "TIMAP", "HOUR", "HH", "MM", "SS", "NLDATMTM"].some((f) => + format.includes(f), + ) && !["DATEAMPM", "DATETIME", "COMMA"].some((f) => format.includes(f)); + + const isDateTimeFormat = () => + ["DATEAMPM", "DATETIME", "NLDATM", "NLDATMAP", "NLDATMW"].some((f) => + format.includes(f), + ); + + const isCurrencyFormat = () => + ["NLMNI", "NLMNL", "NLMNY", "YEN", "DOLLAR", "EURO"].some((f) => + format.includes(f), + ); + + if (type === "num") { + if (isDateFormat()) { + return "date"; + } + if (isTimeFormat()) { + return "time"; + } + if (isDateTimeFormat()) { + return "datetime"; + } + if (isCurrencyFormat()) { + return "currency"; + } + } + + return type; +}; From 179fcb7d2bd932410e72d158dfad35078e50bdfc Mon Sep 17 00:00:00 2001 From: "Danila Grobov (s4642g)" Date: Sat, 3 May 2025 19:57:52 +0300 Subject: [PATCH 05/21] DCO Remediation Commit for Danila Grobov (s4642g) I, Danila Grobov (s4642g) , hereby add my Signed-off-by to this commit: 176ab4bf70ca14db632620f8ac4f38057c9896d5 I, Danila Grobov (s4642g) , hereby add my Signed-off-by to this commit: c12016f55f3e4121dd88cfddd896d2ea51b761cc I, Danila Grobov (s4642g) , hereby add my Signed-off-by to this commit: 9d1aff6e69633917a7a9098de102142dd0ce102d I, Danila Grobov (s4642g) , hereby add my Signed-off-by to this commit: 328b22d928d0def7016e60d0d7ccb4d29999839d Signed-off-by: Danila Grobov (s4642g) --- client/src/connection/itc/script.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/client/src/connection/itc/script.ts b/client/src/connection/itc/script.ts index ea014d67f..62f59d9b2 100644 --- a/client/src/connection/itc/script.ts +++ b/client/src/connection/itc/script.ts @@ -325,6 +325,7 @@ class SASRunner{ } $parsedRows += $parsedRow } + Write-Host $($parsedRows | ConvertTo-Json -Depth 10) } } From 16eedec4d9f0712effd8a79e3407cc6bb891b2b4 Mon Sep 17 00:00:00 2001 From: "Danila Grobov (s4642g)" Date: Sat, 3 May 2025 20:40:15 +0300 Subject: [PATCH 06/21] Support empty arrays in json Signed-off-by: Danila Grobov (s4642g) --- client/src/connection/itc/script.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/connection/itc/script.ts b/client/src/connection/itc/script.ts index 62f59d9b2..f816ff906 100644 --- a/client/src/connection/itc/script.ts +++ b/client/src/connection/itc/script.ts @@ -292,7 +292,7 @@ class SASRunner{ $result | Add-Member -MemberType NoteProperty -Name "rows" -Value $records $result | Add-Member -MemberType NoteProperty -Name "count" -Value $count - Write-Host $($result | ConvertTo-Json -Depth 10) + Write-Host $(ConvertTo-Json -Depth 10 -InputObject $result -Compress) } [void]GetColumns([string]$libname, [string]$memname) { @@ -326,7 +326,7 @@ class SASRunner{ $parsedRows += $parsedRow } - Write-Host $($parsedRows | ConvertTo-Json -Depth 10) + Write-Host $(ConvertTo-Json -Depth 10 -InputObject $parsedRows -Compress) } } `; From 2ab79996a930d512b7d72f74b79699e804b9fec3 Mon Sep 17 00:00:00 2001 From: "Danila Grobov (s4642g)" Date: Tue, 6 May 2025 22:43:26 +0300 Subject: [PATCH 07/21] Fix incorrect label. --- client/src/connection/itc/script.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/connection/itc/script.ts b/client/src/connection/itc/script.ts index f816ff906..fa49cec90 100644 --- a/client/src/connection/itc/script.ts +++ b/client/src/connection/itc/script.ts @@ -308,7 +308,7 @@ class SASRunner{ [System.Reflection.Missing]::Value, # Use the active connection 2, # adOpenDynamic 1, # adLockReadOnly - 1 # adCmdTableDirect + 1 # adCmdText ) $rows = $objRecordSet.GetRows() From e78ff546b0fc4226222df2e31f39aacdfbb9d99a Mon Sep 17 00:00:00 2001 From: "Danila Grobov (s4642g)" Date: Tue, 6 May 2025 23:11:13 +0300 Subject: [PATCH 08/21] Remove unnecessary parameters in data connection DCO Remediation Commit for Danila Grobov (s4642g) I, Danila Grobov (s4642g) , hereby add my Signed-off-by to this commit: 2ab79996a930d512b7d72f74b79699e804b9fec3 Signed-off-by: Danila Grobov (s4642g) --- client/src/connection/itc/script.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/client/src/connection/itc/script.ts b/client/src/connection/itc/script.ts index fa49cec90..d14d421c7 100644 --- a/client/src/connection/itc/script.ts +++ b/client/src/connection/itc/script.ts @@ -97,13 +97,9 @@ class SASRunner{ $this.dataConnection = New-Object -comobject ADODB.Connection $this.dataConnection.Provider = "sas.IOMProvider" - $this.dataConnection.Properties("SAS Workspace ID") = $this.objSAS.UniqueIdentifier - $this.dataConnection.Properties("Data Source") = "Data Source Name" - $this.dataConnection.Properties("SAS Port") = $port - $this.dataConnection.Properties("SAS Machine DNS Name") = $profileHost - $this.dataConnection.Properties("SAS Protocol") = $protocol - $this.dataConnection.Properties("User ID") = $username - $this.dataConnection.Properties("Password") = $password + $this.dataConnection.Properties("Data Source") = ( + "iom-id://" + $this.objSAS.UniqueIdentifier + ) $this.dataConnection.Open() Write-Host "${LineCodes.SessionCreatedCode}" From cd0ef993c6586f657e833c5747fa27fb36b1351a Mon Sep 17 00:00:00 2001 From: "Danila Grobov (s4642g)" Date: Sun, 11 May 2025 23:31:36 +0300 Subject: [PATCH 09/21] Fix tests Signed-off-by: Danila Grobov (s4642g) --- client/test/connection/itc/Coderunner.test.ts | 2 +- .../connection/itc/ItcLibraryAdapter.test.ts | 97 +++++++++++++++---- client/test/connection/itc/index.test.ts | 6 +- 3 files changed, 84 insertions(+), 21 deletions(-) diff --git a/client/test/connection/itc/Coderunner.test.ts b/client/test/connection/itc/Coderunner.test.ts index 8b1f222ff..c8b021f4a 100644 --- a/client/test/connection/itc/Coderunner.test.ts +++ b/client/test/connection/itc/Coderunner.test.ts @@ -6,7 +6,7 @@ import { runCode } from "../../../src/connection/itc/CodeRunner"; import { Session } from "../../../src/connection/session"; export class MockSession extends Session { - private _logFn; + protected _logFn; private _runMap: Record | undefined; public sasSystemLine = "The Sas System"; diff --git a/client/test/connection/itc/ItcLibraryAdapter.test.ts b/client/test/connection/itc/ItcLibraryAdapter.test.ts index 616b70dff..a3b46699e 100644 --- a/client/test/connection/itc/ItcLibraryAdapter.test.ts +++ b/client/test/connection/itc/ItcLibraryAdapter.test.ts @@ -6,24 +6,14 @@ import { TableData, } from "../../../src/components/LibraryNavigator/types"; import * as connection from "../../../src/connection"; -import ItcLibraryAdapter from "../../../src/connection/itc/ItcLibraryAdapter"; import { MockSession } from "./Coderunner.test"; -const mockOutput = (now) => ({ - COLOUTPUT: ` - -first,char,1~last,char,2 -`, +const mockOutput = () => ({ LIBOUTPUT: ` test1,yes~test2,no `, - TABLEDATA: ` - -1234 -{"SASTableData+TEST${now.getHours()}${now.getMinutes()}${now.getSeconds()}0": [["Peter","Parker"],["Tony","Stark"]]} -`, "SELECT COUNT(1)": `1234`, TABLEOUTPUT: ` @@ -31,20 +21,52 @@ test1~test2 `, }); +class DatasetMockSession extends MockSession { + private outputs: Array; + private calls = 0; + public constructor(outputs: Array) { + super(); + this.outputs = outputs; + } + protected async execute(): Promise { + const output = `${this.outputs[this.calls]}`; + this.calls += 1; + this._logFn(output.split("\n").map((line) => ({ line, type: "normal" }))); + } +} + +// I know this is not the best way to do it, but I don't know how to do it properly without migrating everything to jest. +// For the tests to work, we need to mock the uuid library to return a predictable value. +async function getPredictableAdaptor() { + delete require.cache[ + require.resolve("../../../src/connection/itc/CodeRunner") + ]; + // @ts-expect-error Mock + require.cache[require.resolve("uuid")] = { + exports: { + v4: () => "mocked-uuid", + }, + }; + const module = await import("../../../src/connection/itc/ItcLibraryAdapter"); + return module.default; +} + describe("ItcLibraryAdapter tests", () => { let now; let clock; let sessionStub; - before(() => { + beforeEach(() => { now = new Date(); clock = sinon.useFakeTimers(now.getTime()); sessionStub = sinon.stub(connection, "getSession"); - sessionStub.returns(new MockSession(mockOutput(now))); + sessionStub.returns(new MockSession(mockOutput())); }); - after(() => { + afterEach(() => { clock.restore(); sessionStub.restore(); + // Cleaning up after getPredictableAdaptor + delete require.cache[require.resolve("uuid")]; }); it("fetches columns", async () => { @@ -56,20 +78,30 @@ describe("ItcLibraryAdapter tests", () => { readOnly: true, }; + const ItcLibraryAdapter = await getPredictableAdaptor(); const libraryAdapter = new ItcLibraryAdapter(); const expectedColumns = [ { name: "first", type: "char", + format: "$8.", index: 1, }, { - name: "last", - type: "char", + name: "date", + type: "date", + format: "YYMMDD10.", index: 2, }, ]; + const mockOutput = JSON.stringify([ + { index: 1, name: "first", type: "char", format: "$8." }, + { index: 2, name: "date", type: "num", format: "YYMMDD10." }, + ]); + + sessionStub.returns(new DatasetMockSession([mockOutput])); + const response = await libraryAdapter.getColumns(item); expect(response.items).to.eql(expectedColumns); @@ -77,6 +109,7 @@ describe("ItcLibraryAdapter tests", () => { }); it("loads libraries", async () => { + const ItcLibraryAdapter = await getPredictableAdaptor(); const libraryAdapter = new ItcLibraryAdapter(); const expectedLibraries: LibraryItem[] = [ { @@ -110,6 +143,17 @@ describe("ItcLibraryAdapter tests", () => { readOnly: true, }; + const mockOutput = JSON.stringify({ + rows: [ + ["Peter", "Parker"], + ["Tony", "Stark"], + ], + count: 1234, + }); + + const ItcLibraryAdapter = await getPredictableAdaptor(); + sessionStub.returns(new DatasetMockSession([mockOutput])); + const libraryAdapter = new ItcLibraryAdapter(); const expectedTableData: TableData = { rows: [ @@ -133,6 +177,24 @@ describe("ItcLibraryAdapter tests", () => { readOnly: true, }; + const mockOutputColumn = JSON.stringify([ + { index: 1, name: "first", type: "char", format: "$8." }, + { index: 2, name: "last", type: "num", format: "YYMMDD10." }, + ]); + + const mockOutputData = JSON.stringify({ + rows: [ + ["Peter", "Parker"], + ["Tony", "Stark"], + ], + count: 1234, + }); + + const ItcLibraryAdapter = await getPredictableAdaptor(); + sessionStub.returns( + new DatasetMockSession([mockOutputColumn, mockOutputData]), + ); + const libraryAdapter = new ItcLibraryAdapter(); const expectedTableData: TableData = { rows: [ @@ -156,7 +218,7 @@ describe("ItcLibraryAdapter tests", () => { name: "TEST", readOnly: true, }; - + const ItcLibraryAdapter = await getPredictableAdaptor(); const libraryAdapter = new ItcLibraryAdapter(); const response = await libraryAdapter.getTableRowCount(item); @@ -171,6 +233,7 @@ describe("ItcLibraryAdapter tests", () => { type: "library", readOnly: true, }; + const ItcLibraryAdapter = await getPredictableAdaptor(); const libraryAdapter = new ItcLibraryAdapter(); const expectedTables: LibraryItem[] = [ { diff --git a/client/test/connection/itc/index.test.ts b/client/test/connection/itc/index.test.ts index bb077ea76..ac24a39a1 100644 --- a/client/test/connection/itc/index.test.ts +++ b/client/test/connection/itc/index.test.ts @@ -14,7 +14,7 @@ import { WORK_DIR_END_TAG, WORK_DIR_START_TAG, } from "../../../src/connection/itc/const"; -import { scriptContent } from "../../../src/connection/itc/script"; +import { getScript } from "../../../src/connection/itc/script"; import { ITCProtocol, LineCodes } from "../../../src/connection/itc/types"; import { Session } from "../../../src/connection/session"; import { extensionContext } from "../../../src/node/extension"; @@ -101,9 +101,9 @@ describe("ITC connection", () => { ).to.be.true; //using args here allows use of deep equal, that generates a concise diff in the test output on failures - expect(stdinStub.args[0][0]).to.deep.equal(scriptContent + "\n"); + expect(stdinStub.args[0][0]).to.deep.equal(getScript({}) + "\n"); expect(stdinStub.args[1][0]).to.deep.equal( - '$runner = New-Object -TypeName SASRunner -ArgumentList ""\n', + "$runner = New-Object -TypeName SASRunner\n", ); expect(stdinStub.args[2][0]).to.deep.equal( From 019bd3d8bc8b53c8cb5ef1a574ece5e35afd9831 Mon Sep 17 00:00:00 2001 From: Danila Grobov Date: Tue, 13 May 2025 00:04:13 +0300 Subject: [PATCH 10/21] Use proxyquire for mocking uuid. Signed-off-by: Danila Grobov --- .../connection/itc/ItcLibraryAdapter.test.ts | 43 +++++++--------- package-lock.json | 49 +++++++++++++++++++ package.json | 3 +- 3 files changed, 69 insertions(+), 26 deletions(-) diff --git a/client/test/connection/itc/ItcLibraryAdapter.test.ts b/client/test/connection/itc/ItcLibraryAdapter.test.ts index a3b46699e..a02ec6ec6 100644 --- a/client/test/connection/itc/ItcLibraryAdapter.test.ts +++ b/client/test/connection/itc/ItcLibraryAdapter.test.ts @@ -1,5 +1,7 @@ import { expect } from "chai"; +import proxyquire from "proxyquire"; import sinon from "sinon"; +import * as uuid from "uuid"; import { LibraryItem, @@ -34,39 +36,36 @@ class DatasetMockSession extends MockSession { this._logFn(output.split("\n").map((line) => ({ line, type: "normal" }))); } } - -// I know this is not the best way to do it, but I don't know how to do it properly without migrating everything to jest. -// For the tests to work, we need to mock the uuid library to return a predictable value. -async function getPredictableAdaptor() { - delete require.cache[ - require.resolve("../../../src/connection/itc/CodeRunner") - ]; - // @ts-expect-error Mock - require.cache[require.resolve("uuid")] = { - exports: { - v4: () => "mocked-uuid", - }, - }; - const module = await import("../../../src/connection/itc/ItcLibraryAdapter"); - return module.default; -} - describe("ItcLibraryAdapter tests", () => { let now; let clock; let sessionStub; + let uuidStub: sinon.SinonStub; + let ItcLibraryAdapter; beforeEach(() => { now = new Date(); clock = sinon.useFakeTimers(now.getTime()); sessionStub = sinon.stub(connection, "getSession"); sessionStub.returns(new MockSession(mockOutput())); + uuidStub = sinon.stub(uuid, "v4"); + uuidStub.returns("mocked-uuid"); + const codeRunner = proxyquire("../../../src/connection/itc/CodeRunner", { + uuid: { + v4: uuidStub, + }, + }); + ItcLibraryAdapter = proxyquire( + "../../../src/connection/itc/ItcLibraryAdapter", + { + "./CodeRunner": codeRunner, + }, + ).default; }); afterEach(() => { clock.restore(); sessionStub.restore(); - // Cleaning up after getPredictableAdaptor - delete require.cache[require.resolve("uuid")]; + uuidStub.restore(); }); it("fetches columns", async () => { @@ -78,7 +77,6 @@ describe("ItcLibraryAdapter tests", () => { readOnly: true, }; - const ItcLibraryAdapter = await getPredictableAdaptor(); const libraryAdapter = new ItcLibraryAdapter(); const expectedColumns = [ { @@ -109,7 +107,6 @@ describe("ItcLibraryAdapter tests", () => { }); it("loads libraries", async () => { - const ItcLibraryAdapter = await getPredictableAdaptor(); const libraryAdapter = new ItcLibraryAdapter(); const expectedLibraries: LibraryItem[] = [ { @@ -151,7 +148,6 @@ describe("ItcLibraryAdapter tests", () => { count: 1234, }); - const ItcLibraryAdapter = await getPredictableAdaptor(); sessionStub.returns(new DatasetMockSession([mockOutput])); const libraryAdapter = new ItcLibraryAdapter(); @@ -190,7 +186,6 @@ describe("ItcLibraryAdapter tests", () => { count: 1234, }); - const ItcLibraryAdapter = await getPredictableAdaptor(); sessionStub.returns( new DatasetMockSession([mockOutputColumn, mockOutputData]), ); @@ -218,7 +213,6 @@ describe("ItcLibraryAdapter tests", () => { name: "TEST", readOnly: true, }; - const ItcLibraryAdapter = await getPredictableAdaptor(); const libraryAdapter = new ItcLibraryAdapter(); const response = await libraryAdapter.getTableRowCount(item); @@ -233,7 +227,6 @@ describe("ItcLibraryAdapter tests", () => { type: "library", readOnly: true, }; - const ItcLibraryAdapter = await getPredictableAdaptor(); const libraryAdapter = new ItcLibraryAdapter(); const expectedTables: LibraryItem[] = [ { diff --git a/package-lock.json b/package-lock.json index 1a6499ac7..3ddd5f348 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "papaparse": "^5.5.2", "path-browserify": "^1.0.1", "prettier": "^3.3.3", + "proxyquire": "^2.1.3", "sinon": "^20.0.0", "ts-loader": "^9.5.2", "ts-node": "^10.9.2", @@ -3389,6 +3390,19 @@ "node": ">=16.0.0" } }, + "node_modules/fill-keys": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/fill-keys/-/fill-keys-1.0.2.tgz", + "integrity": "sha512-tcgI872xXjwFF4xgQmLxi76GnwJG3g/3isB1l4/G5Z4zrbddGpBjqZCO9oEAcB5wX0Hj/5iQB3toxfO7in1hHA==", + "dev": true, + "dependencies": { + "is-object": "~1.0.1", + "merge-descriptors": "~1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -4128,6 +4142,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz", + "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-plain-obj": { "version": "2.1.0", "dev": true, @@ -4622,6 +4645,15 @@ "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", "dev": true }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -4828,6 +4860,12 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/module-not-found-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/module-not-found-error/-/module-not-found-error-1.0.1.tgz", + "integrity": "sha512-pEk4ECWQXV6z2zjhRZUongnLJNUeGQJ3w6OQ5ctGwD+i5o93qjRQUk2Rt6VdNeu3sEP0AB4LcfvdebpxBRVr4g==", + "dev": true + }, "node_modules/ms": { "version": "2.1.2", "dev": true, @@ -5306,6 +5344,17 @@ "node": ">= 8" } }, + "node_modules/proxyquire": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/proxyquire/-/proxyquire-2.1.3.tgz", + "integrity": "sha512-BQWfCqYM+QINd+yawJz23tbBM40VIGXOdDw3X344KcclI/gtBbdWF6SlQ4nK/bYhF9d27KYug9WzljHC6B9Ysg==", + "dev": true, + "dependencies": { + "fill-keys": "^1.0.2", + "module-not-found-error": "^1.0.1", + "resolve": "^1.11.1" + } + }, "node_modules/pseudo-localization": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/pseudo-localization/-/pseudo-localization-2.4.0.tgz", diff --git a/package.json b/package.json index edaa697cd..06deadec7 100644 --- a/package.json +++ b/package.json @@ -1300,8 +1300,8 @@ "buffer": "6.0.3", "chai": "^4.4.1", "concurrently": "^9.1.0", - "esbuild": "^0.25.0", "cross-env": "^7.0.3", + "esbuild": "^0.25.0", "eslint": "^9.25.1", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^5.2.0", @@ -1311,6 +1311,7 @@ "papaparse": "^5.5.2", "path-browserify": "^1.0.1", "prettier": "^3.3.3", + "proxyquire": "^2.1.3", "sinon": "^20.0.0", "ts-loader": "^9.5.2", "ts-node": "^10.9.2", From 192c22f09dae0bfa334702bb05e2a86456cc7a80 Mon Sep 17 00:00:00 2001 From: Danila Grobov Date: Tue, 13 May 2025 21:55:28 +0300 Subject: [PATCH 11/21] Fix linting problems. Signed-off-by: Danila Grobov --- client/src/connection/itc/ItcLibraryAdapter.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/client/src/connection/itc/ItcLibraryAdapter.ts b/client/src/connection/itc/ItcLibraryAdapter.ts index 4d81f9a3d..4fd8e5cde 100644 --- a/client/src/connection/itc/ItcLibraryAdapter.ts +++ b/client/src/connection/itc/ItcLibraryAdapter.ts @@ -11,7 +11,7 @@ import { TableData, TableRow, } from "../../components/LibraryNavigator/types"; -import { Column, ColumnCollection } from "../rest/api/compute"; +import { ColumnCollection } from "../rest/api/compute"; import { getColumnIconType } from "../util"; import { executeRawCode, runCode } from "./CodeRunner"; import { Config } from "./types"; @@ -251,9 +251,4 @@ const processQueryRows = (response: string): string[] => { .filter((value, index, array) => array.indexOf(value) === index); }; -const hms = () => { - const date = new Date(); - return `${date.getHours()}${date.getMinutes()}${date.getSeconds()}`; -}; - export default ItcLibraryAdapter; From 7e4d8a25a745f84ab44cadf565f4e268dfec1af5 Mon Sep 17 00:00:00 2001 From: Danila Grobov Date: Sun, 25 May 2025 20:43:31 +0300 Subject: [PATCH 12/21] Fix package-lock --- package-lock.json | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2b4bc6cfe..4c10fc0bf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5416,20 +5416,6 @@ "node": ">= 8" } }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/proxyquire": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/proxyquire/-/proxyquire-2.1.3.tgz", @@ -7096,4 +7082,4 @@ } } } -} \ No newline at end of file +} From b5785aa58913a06a07e457f7290ca8e05e873a16 Mon Sep 17 00:00:00 2001 From: "Danila Grobov (s4642g)" Date: Sun, 25 May 2025 20:57:40 +0300 Subject: [PATCH 13/21] Bring the file explorer changes back. --- client/src/connection/itc/script.ts | 209 ++++++++++++++++++++++++++++ 1 file changed, 209 insertions(+) diff --git a/client/src/connection/itc/script.ts b/client/src/connection/itc/script.ts index 291719e05..eca30fff2 100644 --- a/client/src/connection/itc/script.ts +++ b/client/src/connection/itc/script.ts @@ -324,5 +324,214 @@ class SASRunner{ Write-Host $(ConvertTo-Json -Depth 10 -InputObject $parsedRows -Compress) } + + [void]DeleteItemAtPath([string]$filePath,[bool]$recursive) { + if ($recursive) { + $items = $this.GetItemsAtPath($filePath); + for($i = 0; $i -lt $items.Count; $i++) { + if ($items[$i].category -eq 0) { + $this.DeleteItemAtPath($items[$i].uri, $true); + } else { + $this.DeleteItemAtPath($items[$i].uri, $false); + } + } + $this.objSAS.FileService.DeleteFile($filePath) + } else { + $this.objSAS.FileService.DeleteFile($filePath) + } + } + + [void]DeleteFile([string]$filePath) { + $recursive = $true + try { + # If we error out, that means we're trying to get items at a file path, + # which isn't valid (thus, this isn't recursive) + $items = $this.GetItemsAtPath($filePath); + } catch { + $recursive = $false + } + try { + $this.DeleteItemAtPath($filePath, $recursive) + Write-Host (@{success=$true} | ConvertTo-Json) + } catch { + Write-Host (@{success=$false; message=$Error[0].Exception.Message} | ConvertTo-Json) + } + } + + [void]CreateDirectory([string]$folderPath, [string]$folderName) { + try { + $currentFolder = $this.GetItemAtPathWithName($folderPath, $folderName) + if ($currentFolder -ne $null) { + Write-Host (@{success=$false} | ConvertTo-Json) + return; + } + + $uri = $this.objSAS.FileService.MakeDirectory($folderPath, $folderName) + $newFolder = $this.GetItemAtPathWithName($folderPath, $folderName) + Write-Host (@{success=$true; data=$newFolder} | ConvertTo-Json) + } catch { + Write-Host (@{success=$false; message=$Error[0].Exception.Message} | ConvertTo-Json) + } + } + + [void]CreateFile([string]$folderPath, [string]$fileName, [string]$content) { + try { + $currentItem = $this.GetItemAtPathWithName($folderPath, $fileName) + if ($currentItem -ne $null) { + Write-Host (@{success=$false} | ConvertTo-Json) + return; + } + + $fileRefName = "" + $objFile = $this.objSAS.FileService.AssignFileref("", "DISK", $folderPath, "", [ref] $fileRefName) + $assignedName = "" + $outFile = $objFile.AssignMember("", $fileName, "DISK", "", [ref] $assignedName) + $objStream = $outFile.OpenBinaryStream([SAS.StreamOpenMode]::StreamOpenModeForWriting) + $objStream.Write([System.Convert]::FromBase64String($content)); + $objStream.Close() + $this.objSAS.FileService.DeassignFileref($outFile.FilerefName) + $this.objSAS.FileService.DeassignFileref($objFile.FilerefName) + + $newFile = $this.GetItemAtPathWithName($folderPath, $fileName) + Write-Host (@{success=$true; data=$newFile} | ConvertTo-Json) + } catch { + Write-Host (@{success=$false; message=$Error[0].Exception.Message} | ConvertTo-Json) + } + } + + [void]UpdateFile([string]$filePath, [string]$content) { + try { + $fileRefName = "" + $objFile = $this.objSAS.FileService.AssignFileref("", "DISK", $filePath, "", [ref] $fileRefName) + $objStream = $objFile.OpenBinaryStream([SAS.StreamOpenMode]::StreamOpenModeForWriting); + $encoding = [System.Text.Encoding]::UTF8 + $objStream.Write($encoding.GetBytes($content)); + + $objStream.Close() + $this.objSAS.FileService.DeassignFileref($objFile.FilerefName) + + Write-Host (@{success=$true} | ConvertTo-Json) + } catch { + Write-Host (@{success=$false; message=$Error[0].Exception.Message} | ConvertTo-Json) + } + } + + [string]GetDirectorySeparator([string]$path) { + if ($path -Match "/") { + return "/" + } + + return "\\" + } + + [void]RenameFile([string]$oldPath,[string]$newPath,[string]$newName) { + try { + $currentItem = $this.GetItemAtPathWithName($newPath, $newName) + if ($currentItem -ne $null) { + Write-Host (@{success=$false} | ConvertTo-Json) + return; + } + + $this.objSAS.FileService.RenameFile($oldPath,$newPath+$this.GetDirectorySeparator($newPath)+$newName); + $item = $this.GetItemAtPathWithName($newPath, $newName); + Write-Host (@{success=$true; data=$item} | ConvertTo-Json) + } catch { + Write-Host (@{success=$false; message=$Error[0].Exception.Message} | ConvertTo-Json) + } + } + + [void]FetchFileContent([string]$filePath, [string]$outputFile) { + try { + $fileRef = "" + $objFile = $this.objSAS.FileService.AssignFileref("", "DISK", $filePath, "", [ref] $fileRef) + $objStream = $objFile.OpenBinaryStream(1); + [Byte[]] $bytes = 0x0 + + $endOfFile = $false + $byteCount = 0 + $outStream = New-Object System.IO.FileStream($outputFile, [System.IO.FileMode]::OpenOrCreate, [System.IO.FileAccess]::Write) + try { + do + { + $objStream.Read(8192, [ref] $bytes) + $outStream.Write($bytes, 0, $bytes.length) + $endOfFile = $bytes.Length -lt 8192 + $byteCount = $byteCount + $bytes.Length + } while (-not $endOfFile) + } finally { + $objStream.Close() + $outStream.Close() + $this.objSAS.FileService.DeassignFileref($objFile.FilerefName) + } + Write-Host (@{success=$true} | ConvertTo-Json) + } catch { + Write-Host (@{success=$false; message=$Error[0].Exception.Message} | ConvertTo-Json) + } + } + + [object] GetItemAtPathWithName([string]$folderPath, [string]$name) { + $items = $this.GetItemsAtPath($folderPath) + for($i = 0; $i -lt $items.Count; $i++) { + if ($items[$i].name -eq $name) { + return $items[$i] + } + } + return $null; + } + + [object[]] GetItemsAtPath([string]$folderPath) { + $fieldInclusionMask = [boolean[]]@() + # Out data + $listedPath = "" + $names = [string[]]@() + $typeNames = [string[]]@() + $typeCategories = [SAS.FileRefTypeCategory[]]@() + $sizes = [int[]]@() + $modTimes = [DateTime[]]@() + $engines = [string[]]@() + + $mode = [SAS.FileServiceListFilesMode]::FileServiceListFilesModePath + if ($folderPath -eq "/") { + $mode = [SAS.FileServiceListFilesMode]::FileServiceListFilesModeUser + } + + $this.objSAS.FileService.ListFiles( + $folderPath, + $mode, + $fieldInclusionMask, + [ref]$listedPath, + [ref]$names, + [ref]$typeNames, + [ref]$typeCategories, + [ref]$sizes, + [ref]$modTimes, + [ref]$engines + ) + + $output = [object[]]::new($names.Length) + for($i = 0; $i -lt $names.Count; $i++) { + $output[$i] = @{ + uri=$listedPath + $this.GetDirectorySeparator($listedPath) + $names[$i] + name=$names[$i]; + type=$typeNames[$i]; + category=$typeCategories[$i]; + size=$sizes[$i]; + modifiedTimeStamp=$modTimes[$i]; + engine=$engines[$i]; + parentFolderUri=$listedPath + } + } + + return $output + } + + [void]GetChildItems([string]$folderPath) { + try { + $output = $this.GetItemsAtPath($folderPath) + Write-Host (@{success=$true; data=$output} | ConvertTo-Json) + } catch { + Write-Host (@{success=$false; message=$Error[0].Exception.Message} | ConvertTo-Json) + } + } } `; From 207d3254d5bfa29872f17430c837166d95d07357 Mon Sep 17 00:00:00 2001 From: Danila Grobov Date: Sun, 25 May 2025 21:03:39 +0300 Subject: [PATCH 14/21] DCO Remediation Commit for Danila Grobov I, Danila Grobov , hereby add my Signed-off-by to this commit: 7e4d8a25a745f84ab44cadf565f4e268dfec1af5 Signed-off-by: Danila Grobov --- client/src/connection/itc/script.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/connection/itc/script.ts b/client/src/connection/itc/script.ts index eca30fff2..b43e02e25 100644 --- a/client/src/connection/itc/script.ts +++ b/client/src/connection/itc/script.ts @@ -254,7 +254,7 @@ class SASRunner{ [System.Reflection.Missing]::Value, # Use the active connection 2, # adOpenDynamic 1, # adLockReadOnly - 512 # adCmdTableDirect + 512 # adCmdTableDirect ) $records = [List[List[object]]]::new() From 674c87eb07eff9d1acff0374971bc04a9c473bf5 Mon Sep 17 00:00:00 2001 From: "Danila Grobov (s4642g)" Date: Sun, 25 May 2025 21:11:17 +0300 Subject: [PATCH 15/21] DCO Remediation Commit for Danila Grobov (s4642g) I, Danila Grobov (s4642g) , hereby add my Signed-off-by to this commit: b5785aa58913a06a07e457f7290ca8e05e873a16 Signed-off-by: Danila Grobov (s4642g) --- client/src/connection/itc/script.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/connection/itc/script.ts b/client/src/connection/itc/script.ts index b43e02e25..eca30fff2 100644 --- a/client/src/connection/itc/script.ts +++ b/client/src/connection/itc/script.ts @@ -254,7 +254,7 @@ class SASRunner{ [System.Reflection.Missing]::Value, # Use the active connection 2, # adOpenDynamic 1, # adLockReadOnly - 512 # adCmdTableDirect + 512 # adCmdTableDirect ) $records = [List[List[object]]]::new() From 9cfb1f61092c78df48a706ee750c86b38b5c45d9 Mon Sep 17 00:00:00 2001 From: Danila Grobov Date: Wed, 28 May 2025 20:29:28 +0300 Subject: [PATCH 16/21] Fix formatting issues. Signed-off-by: Danila Grobov --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 659d1c49d..1915df7df 100644 --- a/package.json +++ b/package.json @@ -1320,4 +1320,4 @@ "webpack": "^5.99.8", "webpack-cli": "^6.0.1" } -} \ No newline at end of file +} From ce94b23a051614c345f4e2ed449f4cdae80b1d60 Mon Sep 17 00:00:00 2001 From: Danila Grobov Date: Fri, 30 May 2025 23:10:03 +0300 Subject: [PATCH 17/21] Refactor getColumnIconType. Signed-off-by: Danila Grobov --- client/src/connection/util.ts | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/client/src/connection/util.ts b/client/src/connection/util.ts index 1a290cde9..8d8a5fd0f 100644 --- a/client/src/connection/util.ts +++ b/client/src/connection/util.ts @@ -78,19 +78,21 @@ export const getColumnIconType = ({ format.includes(f), ); - if (type === "num") { - if (isDateFormat()) { - return "date"; - } - if (isTimeFormat()) { - return "time"; - } - if (isDateTimeFormat()) { - return "datetime"; - } - if (isCurrencyFormat()) { - return "currency"; - } + if (type !== "num") { + return type; + } + + if (isDateFormat()) { + return "date"; + } + if (isTimeFormat()) { + return "time"; + } + if (isDateTimeFormat()) { + return "datetime"; + } + if (isCurrencyFormat()) { + return "currency"; } return type; From f341dd86fc2b70e1c6b7025df7aa7cef89eca942 Mon Sep 17 00:00:00 2001 From: Dmitry Kolosov Date: Tue, 3 Jun 2025 13:50:26 -0400 Subject: [PATCH 18/21] Updating getLibraries/getTables to use ADO DB connector --- .../src/connection/itc/ItcLibraryAdapter.ts | 42 +++------- client/src/connection/itc/script.ts | 77 +++++++++++++++++-- 2 files changed, 83 insertions(+), 36 deletions(-) diff --git a/client/src/connection/itc/ItcLibraryAdapter.ts b/client/src/connection/itc/ItcLibraryAdapter.ts index f84a5b864..6c3f3171f 100644 --- a/client/src/connection/itc/ItcLibraryAdapter.ts +++ b/client/src/connection/itc/ItcLibraryAdapter.ts @@ -59,27 +59,19 @@ class ItcLibraryAdapter implements LibraryAdapter { count: -1, }; } - public async getLibraries(): Promise<{ items: LibraryItem[]; count: number; }> { - const sql = ` - %let OUTPUT; - proc sql; - select catx(',', libname, readonly) as libname_target into: OUTPUT separated by '~' - from sashelp.vlibnam order by libname asc; - quit; - %put &OUTPUT; %put ; + const code = ` + $runner.GetLibraries() `; - const libNames = processQueryRows( - await this.runCode(sql, "", ""), - ); - - const libraries = libNames.map((lineText): LibraryItem => { - const [libName, readOnlyValue] = lineText.split(","); + const output = await executeRawCode(code); + const rawLibraries = JSON.parse(output).libraries; + const libraries = rawLibraries.map((row: string[]) => { + const [libName, readOnlyValue] = row; return { type: "library", uid: libName, @@ -161,24 +153,14 @@ class ItcLibraryAdapter implements LibraryAdapter { items: LibraryItem[]; count: number; }> { - const sql = ` - %let OUTPUT; - proc sql; - select memname into: OUTPUT separated by '~' - from sashelp.vtable - where libname='${item.name!}' - order by memname asc; - quit; - %put &OUTPUT; %put ; - `; - - const tableNames = processQueryRows( - await this.runCode(sql, "", ""), - ); - const tables = tableNames.map((lineText): LibraryItem => { - const [table] = lineText.split(","); + const code = ` + $runner.GetTables("${item.name}") + `; + const output = await executeRawCode(code); + const rawTables = JSON.parse(output).tables; + const tables = rawTables.map((table: string): LibraryItem => { return { type: "table", uid: `${item.name!}.${table}`, diff --git a/client/src/connection/itc/script.ts b/client/src/connection/itc/script.ts index eca30fff2..103931470 100644 --- a/client/src/connection/itc/script.ts +++ b/client/src/connection/itc/script.ts @@ -243,14 +243,14 @@ class SASRunner{ Write-Host "${LineCodes.ResultsFetchedCode}" } - + [void]GetDatasetRecords([string]$tableName, [int]$start = 0, [int]$limit = 100) { $objRecordSet = New-Object -comobject ADODB.Recordset $objRecordSet.ActiveConnection = $this.dataConnection # This is needed to set the properties for sas formats. $objRecordSet.Properties.Item("SAS Formats").Value = "_ALL_" $objRecordSet.Open( - $tableName, + $tableName, [System.Reflection.Missing]::Value, # Use the active connection 2, # adOpenDynamic 1, # adLockReadOnly @@ -278,7 +278,7 @@ class SASRunner{ $objRecordSet.Close() $objRecordSet.Open( - "SELECT COUNT(1) FROM $tableName", + "SELECT COUNT(1) FROM $tableName", $this.dataConnection, 3, 1, 1 ) # adOpenStatic, adLockReadOnly, adCmdText $count = $objRecordSet.Fields.Item(0).Value @@ -295,8 +295,8 @@ class SASRunner{ $objRecordSet = New-Object -comobject ADODB.Recordset $objRecordSet.ActiveConnection = $this.dataConnection $query = @" - select name, type, format - from sashelp.vcolumn + select name, type, format + from sashelp.vcolumn where libname='$libname' and memname='$memname'; "@ $objRecordSet.Open( @@ -306,7 +306,7 @@ class SASRunner{ 1, # adLockReadOnly 1 # adCmdText ) - + $rows = $objRecordSet.GetRows() $objRecordSet.Close() @@ -533,5 +533,70 @@ class SASRunner{ Write-Host (@{success=$false; message=$Error[0].Exception.Message} | ConvertTo-Json) } } + + [void]GetLibraries() { + $objRecordSet = New-Object -comobject ADODB.Recordset + $objRecordSet.ActiveConnection = $this.dataConnection + $query = @" + select distinct libname, readonly + from sashelp.vlibnam + order by libname asc +"@ + $objRecordSet.Open( + $query, + [System.Reflection.Missing]::Value, # Use the active connection + 2, # adOpenDynamic + 1, # adLockReadOnly + 1 # adCmdText + ) + + $records = [System.Collections.Generic.List[object[]]]::new() + while (-not $objRecordSet.EOF) { + $row = @() + for ($i = 0; $i -lt $objRecordSet.Fields.Count; $i++) { + $row += $objRecordSet.Fields.Item($i).Value + } + $records.Add($row) + $objRecordSet.MoveNext() + } + $objRecordSet.Close() + + $result = New-Object psobject + $result | Add-Member -MemberType NoteProperty -Name "libraries" -Value $records + $result | Add-Member -MemberType NoteProperty -Name "count" -Value $records.Count + + Write-Host $(ConvertTo-Json -Depth 10 -InputObject $result -Compress) + } + + [void]GetTables([string]$libname) { + $objRecordSet = New-Object -comobject ADODB.Recordset + $objRecordSet.ActiveConnection = $this.dataConnection + $query = @" + select memname + from sashelp.vtable + where libname='$libname' + order by memname asc +"@ + $objRecordSet.Open( + $query, + [System.Reflection.Missing]::Value, # Use the active connection + 2, # adOpenDynamic + 1, # adLockReadOnly + 1 # adCmdText + ) + + $records = @() + while (-not $objRecordSet.EOF) { + $records += $objRecordSet.Fields.Item(0).Value + $objRecordSet.MoveNext() + } + $objRecordSet.Close() + + $result = New-Object psobject + $result | Add-Member -MemberType NoteProperty -Name "tables" -Value $records + $result | Add-Member -MemberType NoteProperty -Name "count" -Value $records.Count + + Write-Host $(ConvertTo-Json -Depth 10 -InputObject $result -Compress) + } } `; From 8845e585d103e44072df40400c5b5abdc9859606 Mon Sep 17 00:00:00 2001 From: dmitrymk Date: Wed, 4 Jun 2025 15:06:29 -0300 Subject: [PATCH 19/21] Updating tests --- .../connection/itc/ItcLibraryAdapter.test.ts | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/client/test/connection/itc/ItcLibraryAdapter.test.ts b/client/test/connection/itc/ItcLibraryAdapter.test.ts index a02ec6ec6..501dc9be3 100644 --- a/client/test/connection/itc/ItcLibraryAdapter.test.ts +++ b/client/test/connection/itc/ItcLibraryAdapter.test.ts @@ -11,16 +11,7 @@ import * as connection from "../../../src/connection"; import { MockSession } from "./Coderunner.test"; const mockOutput = () => ({ - LIBOUTPUT: ` - -test1,yes~test2,no - -`, "SELECT COUNT(1)": `1234`, - TABLEOUTPUT: ` - -test1~test2 -`, }); class DatasetMockSession extends MockSession { @@ -107,7 +98,6 @@ describe("ItcLibraryAdapter tests", () => { }); it("loads libraries", async () => { - const libraryAdapter = new ItcLibraryAdapter(); const expectedLibraries: LibraryItem[] = [ { uid: "test1", @@ -125,6 +115,17 @@ describe("ItcLibraryAdapter tests", () => { }, ]; + const mockOutput = JSON.stringify({ + libraries: [ + ["test1", "yes"], + ["test2", "no"], + ], + count: 2, + }); + + sessionStub.returns(new DatasetMockSession([mockOutput])); + + const libraryAdapter = new ItcLibraryAdapter(); const response = await libraryAdapter.getLibraries(); expect(response.items).to.eql(expectedLibraries); @@ -227,7 +228,14 @@ describe("ItcLibraryAdapter tests", () => { type: "library", readOnly: true, }; - const libraryAdapter = new ItcLibraryAdapter(); + + const mockOutput = JSON.stringify({ + tables: ["test1", "test2"], + count: 2, + }); + + sessionStub.returns(new DatasetMockSession([mockOutput])); + const expectedTables: LibraryItem[] = [ { library: "lib", @@ -247,6 +255,7 @@ describe("ItcLibraryAdapter tests", () => { }, ]; + const libraryAdapter = new ItcLibraryAdapter(); const response = await libraryAdapter.getTables(library); expect(response.items).to.eql(expectedTables); From 2cc7de0d1d673b1e7beeb27324f90f1d16e6e854 Mon Sep 17 00:00:00 2001 From: dmitrymk Date: Tue, 10 Jun 2025 15:03:11 -0300 Subject: [PATCH 20/21] Fixing duplicated code in scripts Signed-off-by: dmitrymk DCO Remediation Commit for dmitrymk I, dmitrymk , hereby add my Signed-off-by to this commit: 8845e585d103e44072df40400c5b5abdc9859606 I, dmitrymk , hereby add my Signed-off-by to this commit: 0e4e742f75d5895a59a56f3bb916aaa389dcba92 I, dmitrymk , hereby add my Signed-off-by to this commit: 8d805a88dcb9aca8b12cd133621e39299c412f02 Signed-off-by: dmitrymk --- client/src/connection/itc/script.ts | 81 ----------------------------- 1 file changed, 81 deletions(-) diff --git a/client/src/connection/itc/script.ts b/client/src/connection/itc/script.ts index 46eb0da31..32f0cc4fc 100644 --- a/client/src/connection/itc/script.ts +++ b/client/src/connection/itc/script.ts @@ -325,87 +325,6 @@ class SASRunner{ Write-Host $(ConvertTo-Json -Depth 10 -InputObject $parsedRows -Compress) } - [void]GetDatasetRecords([string]$tableName, [int]$start = 0, [int]$limit = 100) { - $objRecordSet = New-Object -comobject ADODB.Recordset - $objRecordSet.ActiveConnection = $this.dataConnection # This is needed to set the properties for sas formats. - $objRecordSet.Properties.Item("SAS Formats").Value = "_ALL_" - - $objRecordSet.Open( - $tableName, - [System.Reflection.Missing]::Value, # Use the active connection - 2, # adOpenDynamic - 1, # adLockReadOnly - 512 # adCmdTableDirect - ) - - $records = [List[List[object]]]::new() - $fields = $objRecordSet.Fields.Count - - if ($objRecordSet.EOF) { - Write-Host '{"rows": [], "count": 0}' - return - } - - $objRecordSet.AbsolutePosition = $start + 1 - - for ($j = 0; $j -lt $limit -and $objRecordSet.EOF -eq $False; $j++) { - $cell = [List[object]]::new() - for ($i = 0; $i -lt $fields; $i++) { - $cell.Add($objRecordSet.Fields.Item($i).Value) - } - $records.Add($cell) - $objRecordSet.MoveNext() - } - $objRecordSet.Close() - - $objRecordSet.Open( - "SELECT COUNT(1) FROM $tableName", - $this.dataConnection, 3, 1, 1 - ) # adOpenStatic, adLockReadOnly, adCmdText - $count = $objRecordSet.Fields.Item(0).Value - $objRecordSet.Close() - - $result = New-Object psobject - $result | Add-Member -MemberType NoteProperty -Name "rows" -Value $records - $result | Add-Member -MemberType NoteProperty -Name "count" -Value $count - - Write-Host $(ConvertTo-Json -Depth 10 -InputObject $result -Compress) - } - - [void]GetColumns([string]$libname, [string]$memname) { - $objRecordSet = New-Object -comobject ADODB.Recordset - $objRecordSet.ActiveConnection = $this.dataConnection - $query = @" - select name, type, format - from sashelp.vcolumn - where libname='$libname' and memname='$memname'; -"@ - $objRecordSet.Open( - $query, - [System.Reflection.Missing]::Value, # Use the active connection - 2, # adOpenDynamic - 1, # adLockReadOnly - 1 # adCmdText - ) - - $rows = $objRecordSet.GetRows() - - $objRecordSet.Close() - - $parsedRows = @() - for ($i = 0; $i -lt $rows.GetLength(1); $i++) { - $parsedRow = [PSCustomObject]@{ - index = $i + 1 - name = $rows[0, $i] - type = $rows[1, $i] - format = $rows[2, $i] - } - $parsedRows += $parsedRow - } - - Write-Host $(ConvertTo-Json -Depth 10 -InputObject $parsedRows -Compress) - } - [void]DeleteItemAtPath([string]$filePath,[bool]$recursive) { if ($recursive) { $items = $this.GetItemsAtPath($filePath); From 72aad6a5c13f251f0138674fc8a2aa8798171914 Mon Sep 17 00:00:00 2001 From: dmitrymk Date: Wed, 11 Jun 2025 12:37:29 -0300 Subject: [PATCH 21/21] Removing trailing spaces in script.ts Signed-off-by: dmitrymk dmitry.m.kolosov@gmail.com DCO Remediation Commit for dmitrymk dmitry.m.kolosov@gmail.com I, dmitrymk dmitry.m.kolosov@gmail.com, hereby add my Signed-off-by to this commit: f341dd86fc2b70e1c6b7025df7aa7cef89eca942 Signed-off-by: dmitrymk dmitry.m.kolosov@gmail.com --- client/src/connection/itc/script.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/client/src/connection/itc/script.ts b/client/src/connection/itc/script.ts index 32f0cc4fc..103931470 100644 --- a/client/src/connection/itc/script.ts +++ b/client/src/connection/itc/script.ts @@ -243,14 +243,14 @@ class SASRunner{ Write-Host "${LineCodes.ResultsFetchedCode}" } - + [void]GetDatasetRecords([string]$tableName, [int]$start = 0, [int]$limit = 100) { $objRecordSet = New-Object -comobject ADODB.Recordset $objRecordSet.ActiveConnection = $this.dataConnection # This is needed to set the properties for sas formats. $objRecordSet.Properties.Item("SAS Formats").Value = "_ALL_" $objRecordSet.Open( - $tableName, + $tableName, [System.Reflection.Missing]::Value, # Use the active connection 2, # adOpenDynamic 1, # adLockReadOnly @@ -278,7 +278,7 @@ class SASRunner{ $objRecordSet.Close() $objRecordSet.Open( - "SELECT COUNT(1) FROM $tableName", + "SELECT COUNT(1) FROM $tableName", $this.dataConnection, 3, 1, 1 ) # adOpenStatic, adLockReadOnly, adCmdText $count = $objRecordSet.Fields.Item(0).Value @@ -295,8 +295,8 @@ class SASRunner{ $objRecordSet = New-Object -comobject ADODB.Recordset $objRecordSet.ActiveConnection = $this.dataConnection $query = @" - select name, type, format - from sashelp.vcolumn + select name, type, format + from sashelp.vcolumn where libname='$libname' and memname='$memname'; "@ $objRecordSet.Open( @@ -306,7 +306,7 @@ class SASRunner{ 1, # adLockReadOnly 1 # adCmdText ) - + $rows = $objRecordSet.GetRows() $objRecordSet.Close()