Merge remote-tracking branch 'refs/remotes/Microsoft/master'
@@ -1,49 +0,0 @@
|
||||
# use glob syntax.
|
||||
syntax: glob
|
||||
*.ser
|
||||
*.class
|
||||
*~
|
||||
*.bak
|
||||
#*.off
|
||||
*.old
|
||||
|
||||
# eclipse conf file
|
||||
.settings
|
||||
.classpath
|
||||
.project
|
||||
.manager
|
||||
.scala_dependencies
|
||||
|
||||
# idea
|
||||
.idea
|
||||
*.iml
|
||||
|
||||
#visual studio
|
||||
.suo
|
||||
|
||||
# building
|
||||
target
|
||||
build
|
||||
null
|
||||
tmp*
|
||||
temp*
|
||||
dist
|
||||
test-output
|
||||
build.log
|
||||
out
|
||||
packages
|
||||
|
||||
# other scm
|
||||
.svn
|
||||
.CVS
|
||||
.hg*
|
||||
|
||||
# switch to regexp syntax.
|
||||
# syntax: regexp
|
||||
# ^\.pc/
|
||||
|
||||
#SHITTY output not in target directory
|
||||
build.log
|
||||
|
||||
#dropbox
|
||||
.DS_Store
|
||||
|
Before Width: | Height: | Size: 128 KiB |
|
Before Width: | Height: | Size: 128 KiB |
@@ -1,125 +0,0 @@
|
||||
# In-Memory & Columnar Store Code Snack
|
||||
In this code snack, developers will experience the benefit of performing real-time operation analytics enabled by leveraging a memory optimized table in combination with a columnstore index. The Visual Studio project contains a load generator that will be used to simulate a write heavy workload. They will initially run the simulator against a disk based table with a clustered index (btree) and take note of the rows inserted per second, and will measure the performance of a provided analytics query while the system is under the heavy write load. They will then author the T-SQL to create the memory optimized table with a columnstore index, update the load generator to target the memory optimized table and observe the improved performance characteristics. This code snack targets SQL Server on Linux.
|
||||
|
||||
## Requirements
|
||||
- Visual Studio 2015 with Update 3 (or later)
|
||||
- [SQL Server Data Tools for Visual Studio 2015](https://msdn.microsoft.com/en-us/mt186501)
|
||||
- [SQL Server on Linux](https://www.microsoft.com/en-us/sql-server/sql-server-on-linux) running in [Docker](https://docs.docker.com/engine/installation/#/on-macos-and-windows)
|
||||
- Your SQL on Linux Server should have at least 4 GB of RAM
|
||||
|
||||
## Clone the provided project
|
||||
Clone this repo on to your local machine.
|
||||
The recommended path is C:\In-Memory and Columnar\
|
||||
|
||||
## Download the sample data
|
||||
This project requires a sample set of data you will load into SQL Server.
|
||||
Download the data from: [http://bit.ly/2envb8m](http://bit.ly/2envb8m)
|
||||
|
||||
## Copy the sample data to your SQL on Linux host
|
||||
1. On your Docker host machine, first grab the container ID of your SQL Server on Linux container. The Container ID is the value present in the first column.
|
||||
```
|
||||
docker ps
|
||||
```
|
||||
2. Copy over the sample data from the host to the container running SQL on Linux by using docker cp as follows (replace ContainerID with the container ID your retrieved). Note that the path /var/opt/mssql will map to C: in any T-SQL scripts executed against this server.
|
||||
```
|
||||
docker cp datapoints.bcp [ContainerID]:/var/opt/mssql/data/datapoints.bcp
|
||||
```
|
||||
3. Verify that the copy was successful. By connecting to bash within the container. Connect to your container (substitute your container ID in the command below) and list out the files:
|
||||
```
|
||||
docker exec -t -i [ContainerID] /bin/bash
|
||||
ls /var/opt/mssql/data -1 -s -h
|
||||
```
|
||||
4. You should see datapoints.bcp in the listing
|
||||
```
|
||||
-rw-r--r-- 1 root root 256 Nov 13 22:49 Entropy.bin
|
||||
-rw-r--r-- 1 root root 14M Nov 13 22:50 MSDBData.mdf
|
||||
-rw-r--r-- 1 root root 768K Nov 14 22:48 MSDBLog.ldf
|
||||
-rw-r--r-- 1 root root 161M Nov 11 04:18 datapoints.bcp
|
||||
-rw-r--r-- 1 root root 4.0M Nov 14 22:55 master.mdf
|
||||
-rw-r--r-- 1 root root 768K Nov 14 23:07 mastlog.ldf
|
||||
-rw-r--r-- 1 root root 8.0M Nov 14 23:00 model.mdf
|
||||
-rw-r--r-- 1 root root 8.0M Nov 14 23:00 modellog.ldf
|
||||
-rw-r--r-- 1 root root 264M Nov 14 23:07 tempdb.mdf
|
||||
-rw-r--r-- 1 root root 8.0M Nov 14 23:07 templog.ldf
|
||||
drwxr-xr-x 3 root root 4.0K Nov 14 23:01 xtp
|
||||
```
|
||||
5. You are all set to continue the lab in Visual Studio 2015.
|
||||
|
||||
## Create the database and tables
|
||||
1. Open the SqlLoadgenerator solution using Visual Studio 2015.
|
||||
2. From Solution Explorer, expand the SqlGenerator solution, then SQL Resources folder and open "Create Database.sql".
|
||||
3. Adjust the file paths for the FILENAME attributes if you installed SQL Server to a different location.
|
||||
4. Select the Execute button
|
||||
5. In the Connect dialog, provide your server name, authentication mode, username and password (as appropriate).
|
||||
6. Wait for the script to complete successfully.
|
||||
7. Within Visual Studio, open "Create Table- Disk Based.sql"
|
||||
8. Execute the script to create the DataPointsDiskBased table.
|
||||
This table will be used to store simulated IoT device telemetry, using traditional disk based table as well as clustered and non-clustered indexes on the fields commonly used in both point queries and analytic queries.
|
||||
9. Within Visual Studio, open "Create Table- In Memory.sql"
|
||||
10. Execute the script to create the DataPointsInMem table.
|
||||
This table will be used to store the same simulated IoT device telemetry, but this time using a memory optimized table as well as clustered column store index against all fields (which will support analytic queries) and non-clustered hash indexes on the id field (which will support point lookups common to transactional queries).
|
||||
```
|
||||
CREATE TABLE [DataPointsInMem] (
|
||||
-- ID should be a Primary Key, fields with a b-tree or hash index
|
||||
Id bigint IDENTITY NOT NULL PRIMARY KEY NONCLUSTERED HASH WITH (BUCKET_COUNT = 30000000),
|
||||
[Value] decimal(18,5),
|
||||
[TimestampUtc] datetime,
|
||||
DeviceId int,
|
||||
-- This table should have a columnar index
|
||||
INDEX Transactions_CCI CLUSTERED COLUMNSTORE
|
||||
) WITH (
|
||||
-- This should be an in-memory table
|
||||
MEMORY_OPTIMIZED = ON
|
||||
);
|
||||
|
||||
-- In-memory tables should auto-elevate their transaction level to Snapshot
|
||||
ALTER DATABASE CURRENT SET MEMORY_OPTIMIZED_ELEVATE_TO_SNAPSHOT=ON ;
|
||||
```
|
||||
|
||||
## Load initial data
|
||||
|
||||
1. Within Visual Studio, open "Load Sample Data.sql"
|
||||
2. Adjust the path to the DataPoint.bcp file so it matches the location of your BCP file within the SQL on Linux container (if necessary) and save the script. Recall the path /var/opt/mssql will map to C: in any T-SQL scripts executed against SQL Server on Linux.
|
||||
3. Execute the script to load each table with 4 million rows worth of sample data. This will take some time to complete.
|
||||
|
||||
## Execute the sample analytics query
|
||||
|
||||
1. Within Visual Studio, open "SampleQueries - DiskBased.sql".
|
||||
2. Execute the script to summarize the time series data stored in the disk based table.
|
||||
3. When the script completes, observe that 334 rows were returned.Take note of how long the query took to execute. Query time is shown in the bottom right of the document window in Visual Studio.
|
||||
![alt text][Disk Based Results]
|
||||
|
||||
[Disk Based Results]: Images/DiskBasedResults.png "Disk Based Results"
|
||||
4. Now, execute the script to summarize the time series data stored in the memory-optimized table, in "SampleQueries - InMemory.sql".
|
||||
When the script completes, observe that 334 rows were returned.Take note of how long the query took to execute.
|
||||
You should notice that the performance of the query against the memory-optimized table runs between 2x-10x faster than the same query, running against the same data stored in a disk based table. Query time is shown in the bottom right of the document window in Visual Studio.
|
||||
![alt text][In-Memory Results]
|
||||
|
||||
[In-Memory Results]: Images/InMemoryResults.png "In-Memory Results"
|
||||
|
||||
## Execute the queries under load
|
||||
|
||||
1. Within Visual Studio, Solution Explorer, expand the SqlLoadGenerator project and then open "App.config".
|
||||
2. Locate the connection string with the name "SqlConnection" and modify it so it points to your instance of SQL Server on Linux.
|
||||
3. Save the App.config.
|
||||
4. From the Debug menu, select Start Without Debugging.
|
||||
5. At the prompt, choose option 1 to target the disk based table.
|
||||
You should see log entries when every 1000 rows are inserted.
|
||||
Leave the console running (it should run for about 3 minutes) and return to Visual Studio.
|
||||
6. Open "SampleQueries - DiskBased.sql".
|
||||
7. Execute the script to summarize the time series data stored in the disk based table.
|
||||
8. Observe that more than 334 rows were returned.Take note of how long the query took to execute.
|
||||
9. Repeat the query a few times, waiting a few seconds in between queries to get a sense of how long the query takes, even as new rows are inserted by the load generator.
|
||||
10. Close the console load generator.
|
||||
11. Run the SqlLoadGenerator again.
|
||||
This time at the prompt, choose option 2 to target the memory-optimized table.
|
||||
You should see log entries when every 1000 rows are inserted.
|
||||
12. Leave the console running (it should run for about 3 minutes) and return to Visual Studio.
|
||||
13. Open "SampleQueries - In Memory.sql".
|
||||
14. Execute the script to summarize the time series data stored in the disk based table.
|
||||
15. Observe that more than 334 rows were returned.Take note of how long the query took to execute.
|
||||
Repeat the query a few times, waiting a few seconds in between queries to get a sense of how long the query takes, even as new rows are inserted by the load generator.
|
||||
16. Close the console load generator.
|
||||
|
||||
## Conclusion
|
||||
You should observe that while neither query was affected by the heavy insert load, the query against the analytics query continued to run 2x-10x faster than the same query against the disk-based table.
|
||||
@@ -1,15 +0,0 @@
|
||||
|
||||
-- For SQL Server 2016, create the Database with a master data file (mdf),
|
||||
-- the log data file (ldf) and a separate filegroup to support memory-optimized tables.
|
||||
CREATE DATABASE [Telemetry]
|
||||
CONTAINMENT = NONE
|
||||
ON PRIMARY
|
||||
( NAME = N'Telemetry', FILENAME = N'C:\data\Telemetry.mdf' , SIZE = 128MB , MAXSIZE = UNLIMITED, FILEGROWTH = 65536KB ),
|
||||
FILEGROUP [TelemetryInMem] CONTAINS MEMORY_OPTIMIZED_DATA DEFAULT
|
||||
( NAME = N'Telemetry_mem', FILENAME = N'C:\data\Telemetry_mem' , MAXSIZE = UNLIMITED)
|
||||
LOG ON
|
||||
( NAME = N'Telemetry_log', FILENAME = N'C:\data\Telemetry_log.ldf' , SIZE = 128MB , MAXSIZE = 2048GB , FILEGROWTH = 65536KB )
|
||||
GO
|
||||
|
||||
ALTER DATABASE [Telemetry] SET COMPATIBILITY_LEVEL = 130
|
||||
GO
|
||||
@@ -1,24 +0,0 @@
|
||||
USE Telemetry;
|
||||
GO
|
||||
|
||||
DROP INDEX IF EXISTS dbo.DataPointsDiskBased.IX_DeviceId;
|
||||
DROP INDEX IF EXISTS dbo.DataPointsDiskBased.IX_Timestamp;
|
||||
DROP TABLE IF EXISTS dbo.DataPointsDiskBased;
|
||||
|
||||
CREATE TABLE [DataPointsDiskBased] (
|
||||
Id bigint IDENTITY NOT NULL PRIMARY KEY CLUSTERED,
|
||||
[Value] decimal(18,5),
|
||||
[TimestampUtc] datetime,
|
||||
DeviceId int,
|
||||
);
|
||||
|
||||
CREATE NONCLUSTERED INDEX IX_DeviceId
|
||||
ON dbo.DataPointsDiskBased (DeviceId);
|
||||
GO
|
||||
|
||||
CREATE NONCLUSTERED INDEX IX_Timestamp
|
||||
ON dbo.DataPointsDiskBased (TimestampUtc);
|
||||
GO
|
||||
|
||||
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
USE Telemetry;
|
||||
GO
|
||||
|
||||
DROP INDEX IF EXISTS dbo.DataPointsInMem.IX_DeviceId;
|
||||
DROP INDEX IF EXISTS dbo.DataPointsInMem.IX_Timestamp;
|
||||
DROP TABLE IF EXISTS dbo.DataPointsInMem;
|
||||
|
||||
CREATE TABLE [DataPointsInMem] (
|
||||
-- ID should be a Primary Key, fields with a b-tree or hash index
|
||||
Id bigint IDENTITY NOT NULL PRIMARY KEY NONCLUSTERED HASH WITH (BUCKET_COUNT = 30000000),
|
||||
[Value] decimal(18,5),
|
||||
[TimestampUtc] datetime,
|
||||
DeviceId int,
|
||||
-- This table should have a columnar index
|
||||
INDEX Transactions_CCI CLUSTERED COLUMNSTORE
|
||||
) WITH (
|
||||
-- This should be an in-memory table
|
||||
MEMORY_OPTIMIZED = ON
|
||||
);
|
||||
|
||||
-- In-memory tables should auto-elevate their transaction level to Snapshot
|
||||
ALTER DATABASE CURRENT SET MEMORY_OPTIMIZED_ELEVATE_TO_SNAPSHOT=ON ;
|
||||
@@ -1,17 +0,0 @@
|
||||
USE Telemetry;
|
||||
GO
|
||||
|
||||
BULK INSERT Telemetry.dbo.DataPointsDiskBased
|
||||
FROM 'C:\data\datapoints.bcp'
|
||||
WITH (
|
||||
DATAFILETYPE = 'native'
|
||||
);
|
||||
GO
|
||||
|
||||
BULK INSERT Telemetry.dbo.DataPointsInMem
|
||||
FROM 'C:\data\datapoints.bcp'
|
||||
WITH (
|
||||
DATAFILETYPE = 'native'
|
||||
);
|
||||
GO
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
USE Telemetry;
|
||||
|
||||
SELECT
|
||||
Count(*) Counted,
|
||||
Max([Value]) MaxValue,
|
||||
Avg([Value]) AvgValue,
|
||||
Min([Value]) MinValue,
|
||||
DatePart(YYYY, TimestampUtc) [year],
|
||||
DatePart(MM, TimestampUtc) [month],
|
||||
DatePart(DD, TimestampUtc) [day],
|
||||
DatePart(hh, TimestampUtc) [hour],
|
||||
DatePart(mi, TimestampUtc) [minute],
|
||||
DatePart(ss, TimestampUtc) [second]
|
||||
FROM DataPointsDiskBased
|
||||
GROUP BY
|
||||
DatePart(YYYY, TimestampUtc),
|
||||
DatePart(MM, TimestampUtc),
|
||||
DatePart(DD, TimestampUtc),
|
||||
DatePart(hh, TimestampUtc),
|
||||
DatePart(mi, TimestampUtc),
|
||||
DatePart(ss, TimestampUtc)
|
||||
@@ -1,24 +0,0 @@
|
||||
USE Telemetry;
|
||||
|
||||
SELECT
|
||||
Count(*) Counted,
|
||||
Max([Value]) MaxValue,
|
||||
Avg([Value]) AvgValue,
|
||||
Min([Value]) MinValue,
|
||||
DatePart(YYYY, TimestampUtc) [year],
|
||||
DatePart(MM, TimestampUtc) [month],
|
||||
DatePart(DD, TimestampUtc) [day],
|
||||
DatePart(hh, TimestampUtc) [hour],
|
||||
DatePart(mi, TimestampUtc) [minute],
|
||||
DatePart(ss, TimestampUtc) [second]
|
||||
FROM DataPointsInMem
|
||||
GROUP BY
|
||||
DatePart(YYYY, TimestampUtc),
|
||||
DatePart(MM, TimestampUtc),
|
||||
DatePart(DD, TimestampUtc),
|
||||
DatePart(hh, TimestampUtc),
|
||||
DatePart(mi, TimestampUtc),
|
||||
DatePart(ss, TimestampUtc)
|
||||
|
||||
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<configSections>
|
||||
<sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
|
||||
<section name="SqlLoadGenerator.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
|
||||
</sectionGroup>
|
||||
</configSections>
|
||||
|
||||
<connectionStrings>
|
||||
<add name="SqlConnection"
|
||||
connectionString="data source=192.168.5.105;initial catalog=telemetry;user id=sa;password=Abc1234567890;MultipleActiveResultSets=True;App=LoadGenerator" providerName="System.Data.SqlClient" />
|
||||
</connectionStrings>
|
||||
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
|
||||
</startup>
|
||||
|
||||
<userSettings>
|
||||
<SqlLoadGenerator.Properties.Settings>
|
||||
<setting name="Spike_NumRowsToInsert" serializeAs="String">
|
||||
<value>200000</value>
|
||||
</setting>
|
||||
<setting name="Spike_NumParallelClients" serializeAs="String">
|
||||
<value>1</value>
|
||||
</setting>
|
||||
</SqlLoadGenerator.Properties.Settings>
|
||||
</userSettings>
|
||||
</configuration>
|
||||
@@ -1,157 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Data.SqlClient;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace SqlLoadGenerator
|
||||
{
|
||||
class Program
|
||||
{
|
||||
static string _connectionString;
|
||||
static int _numRowsToInsert = Properties.Settings.Default.Spike_NumRowsToInsert;
|
||||
static int _numTaskPerSpike = Properties.Settings.Default.Spike_NumParallelClients;
|
||||
static Stopwatch _stopwatch;
|
||||
static string _tableName = "DataPointsDiskBased";
|
||||
|
||||
static void Main(string[] args)
|
||||
{
|
||||
Console.WriteLine("Which table would you like to target for load? (press 1 or 2)");
|
||||
Console.WriteLine("1. Disk Based Table");
|
||||
Console.WriteLine("2. Memory-Optimized Table");
|
||||
if (Console.ReadKey().KeyChar == '1')
|
||||
{
|
||||
_tableName = "DataPointsDiskBased";
|
||||
}
|
||||
else
|
||||
{
|
||||
_tableName = "DataPointsInMem";
|
||||
}
|
||||
Console.WriteLine("");
|
||||
|
||||
_connectionString = System.Configuration.ConfigurationManager.ConnectionStrings["SqlConnection"].ConnectionString;
|
||||
|
||||
_stopwatch = Stopwatch.StartNew();
|
||||
|
||||
List<Task> tasks = new List<Task>();
|
||||
|
||||
tasks.AddRange(ScheduleLoadSpike(_numTaskPerSpike));
|
||||
|
||||
Task.WaitAll(tasks.ToArray());
|
||||
Console.WriteLine("Tasks completed.");
|
||||
|
||||
_stopwatch.Stop();
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Cyan;
|
||||
Console.WriteLine("{0} hours {1} minutes {2} seconds elapsed.",
|
||||
_stopwatch.Elapsed.Hours, _stopwatch.Elapsed.Minutes, _stopwatch.Elapsed.Seconds);
|
||||
|
||||
Console.ReadLine();
|
||||
}
|
||||
|
||||
static Task[] ScheduleLoadSpike(int numTasks)
|
||||
{
|
||||
Task[] tasks = new Task[numTasks];
|
||||
for (int i = 0; i < numTasks; i++)
|
||||
{
|
||||
tasks[i] = Task.Run(() => GenerateLoadSpike());
|
||||
}
|
||||
|
||||
return tasks;
|
||||
}
|
||||
|
||||
static int _numTasks = 0;
|
||||
static int GenerateLoadSpike()
|
||||
{
|
||||
Random Rand = new Random();
|
||||
|
||||
int taskID = System.Threading.Interlocked.Increment(ref _numTasks);
|
||||
Console.WriteLine("{0}: Preparing load spike...", taskID);
|
||||
|
||||
int numRowsAffected = 0;
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
SqlConnection conn = new SqlConnection(_connectionString);
|
||||
|
||||
string commandText = String.Format("INSERT [dbo].[{0}] ([Value], TimestampUtc, DeviceId) " +
|
||||
"VALUES (@Value, @TimestampUtc, @DeviceId)", _tableName);
|
||||
|
||||
Random r = new Random(1);
|
||||
|
||||
conn.Open();
|
||||
|
||||
for (int i = 0; i < _numRowsToInsert; i++)
|
||||
{
|
||||
// if a transient error closed our connection, create a new one and open it
|
||||
if (conn.State == System.Data.ConnectionState.Closed)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine("{0}: Re-creating closed connection", taskID);
|
||||
Console.ResetColor();
|
||||
conn = new SqlConnection(_connectionString);
|
||||
conn.Open();
|
||||
}
|
||||
|
||||
double value = 9999 + 100 * Rand.NextDouble();
|
||||
List<SqlParameter> parameters = new List<SqlParameter>() {
|
||||
new SqlParameter("@Value", value),
|
||||
new SqlParameter("@TimestampUtc", DateTime.UtcNow),
|
||||
new SqlParameter("@DeviceId", taskID)
|
||||
};
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
using (SqlCommand cmd = new SqlCommand(commandText, conn))
|
||||
{
|
||||
cmd.CommandType = System.Data.CommandType.Text;
|
||||
cmd.Parameters.AddRange(parameters.ToArray());
|
||||
numRowsAffected += cmd.ExecuteNonQuery();
|
||||
|
||||
}
|
||||
}
|
||||
catch (SqlException sqlex)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.WriteLine(sqlex.Message);
|
||||
Console.ResetColor();
|
||||
|
||||
System.Threading.Thread.Sleep(200);
|
||||
}
|
||||
catch (Exception cmdex)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Blue;
|
||||
Console.WriteLine(cmdex.Message);
|
||||
Console.ResetColor();
|
||||
|
||||
}
|
||||
|
||||
if (i % 1000 == 0)
|
||||
{
|
||||
Console.WriteLine("{0}: Inserted {1} new rows so far", taskID, numRowsAffected);
|
||||
}
|
||||
}
|
||||
|
||||
conn.Close();
|
||||
conn.Dispose();
|
||||
|
||||
Console.WriteLine("{0}: Inserted {1} new rows", taskID, numRowsAffected);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine(ex.Message);
|
||||
Console.ResetColor();
|
||||
}
|
||||
|
||||
Console.WriteLine("{0}: Finished with load spike.", taskID);
|
||||
return numRowsAffected;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("SqlLoadGenerator")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("SqlLoadGenerator")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2016")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("59486e0e-6932-4191-94ff-04082db46472")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
@@ -1,50 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace SqlLoadGenerator.Properties {
|
||||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")]
|
||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
|
||||
|
||||
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||
|
||||
public static Settings Default {
|
||||
get {
|
||||
return defaultInstance;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("6000000")]
|
||||
public int Spike_NumRowsToInsert {
|
||||
get {
|
||||
return ((int)(this["Spike_NumRowsToInsert"]));
|
||||
}
|
||||
set {
|
||||
this["Spike_NumRowsToInsert"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("1")]
|
||||
public int Spike_NumParallelClients {
|
||||
get {
|
||||
return ((int)(this["Spike_NumParallelClients"]));
|
||||
}
|
||||
set {
|
||||
this["Spike_NumParallelClients"] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)" GeneratedClassNamespace="SqlLoadGenerator.Properties" GeneratedClassName="Settings">
|
||||
<Profiles />
|
||||
<Settings>
|
||||
<Setting Name="Spike_NumRowsToInsert" Type="System.Int32" Scope="User">
|
||||
<Value Profile="(Default)">6000000</Value>
|
||||
</Setting>
|
||||
<Setting Name="Spike_NumParallelClients" Type="System.Int32" Scope="User">
|
||||
<Value Profile="(Default)">1</Value>
|
||||
</Setting>
|
||||
</Settings>
|
||||
</SettingsFile>
|
||||
@@ -1,70 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{59486E0E-6932-4191-94FF-04082DB46472}</ProjectGuid>
|
||||
<OutputType>Exe</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>SqlLoadGenerator</RootNamespace>
|
||||
<AssemblyName>SqlLoadGenerator</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Configuration" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Properties\Settings.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||
<DependentUpon>Settings.settings</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
<None Include="Properties\Settings.settings">
|
||||
<Generator>SettingsSingleFileGenerator</Generator>
|
||||
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
@@ -1 +0,0 @@
|
||||
Coming soon
|
||||
|
Before Width: | Height: | Size: 132 KiB |
|
Before Width: | Height: | Size: 132 KiB |
@@ -1,95 +0,0 @@
|
||||
# In-Memory & Columnar Store Code Snack (SQL Server 2016)
|
||||
In this code snack, developers will experience the benefit of performing real-time operation analytics enabled by leveraging a memory optimized table in combination with a columnstore index. The Visual Studio project contains a load generator that will be used to simulate a write heavy workload. They will initially run the simulator against a disk based table with a clustered index (btree) and take note of the rows inserted per second, and will measure the performance of a provided analytics query while the system is under the heavy write load. They will then author the T-SQL to create the memory optimized table with a columnstore index, update the load generator to target the memory optimized table and observe the improved performance characteristics.
|
||||
|
||||
## Requirements
|
||||
- Visual Studio 2015 with Update 3 (or later)
|
||||
- [SQL Server Data Tools for Visual Studio 2015](https://msdn.microsoft.com/en-us/mt186501)
|
||||
- SQL Server 2016 Developer Edition (or higher)
|
||||
- Your developer machine should have at least 8 GB of RAM
|
||||
|
||||
## Clone the provided project
|
||||
Clone this repo on to your local machine.
|
||||
The recommended path is C:\In-Memory and Columnar\
|
||||
|
||||
## Download the sample data
|
||||
This project requires a sample set of data you will load into SQL Server.
|
||||
Download the data from: [http://bit.ly/2envb8m](http://bit.ly/2envb8m)
|
||||
|
||||
## Create the database and tables
|
||||
1. Open the SqlLoadgenerator solution using Visual Studio 2015.
|
||||
2. From Solution Explorer, expand the SqlGenerator solution, then SQL Resources folder and open "Create Database.sql".
|
||||
3. Adjust the file paths for the FILENAME attributes if you installed SQL Server to a different location.
|
||||
4. Select the Execute button
|
||||
5. In the Connect dialog, provide your server name, authentication mode, username and password (as appropriate).
|
||||
6. Wait for the script to complete successfully.
|
||||
7. Within Visual Studio, open "Create Table- Disk Based.sql"
|
||||
8. Execute the script to create the DataPointsDiskBased table.
|
||||
This table will be used to store simulated IoT device telemetry, using traditional disk based table as well as clustered and non-clustered indexes on the fields commonly used in both point queries and analytic queries.
|
||||
9. Within Visual Studio, open "Create Table- In Memory.sql"
|
||||
10. Execute the script to create the DataPointsInMem table.
|
||||
This table will be used to store the same simulated IoT device telemetry, but this time using a memory optimized table as well as clustered column store index against all fields (which will support analytic queries) and non-clustered hash indexes on the id field (which will support point lookups common to transactional queries).
|
||||
```
|
||||
CREATE TABLE [DataPointsInMem] (
|
||||
-- ID should be a Primary Key, fields with a b-tree or hash index
|
||||
Id bigint IDENTITY NOT NULL PRIMARY KEY NONCLUSTERED HASH WITH (BUCKET_COUNT = 30000000),
|
||||
[Value] decimal(18,5),
|
||||
[TimestampUtc] datetime,
|
||||
DeviceId int,
|
||||
-- This table should have a columnar index
|
||||
INDEX Transactions_CCI CLUSTERED COLUMNSTORE
|
||||
) WITH (
|
||||
-- This should be an in-memory table
|
||||
MEMORY_OPTIMIZED = ON
|
||||
);
|
||||
|
||||
-- In-memory tables should auto-elevate their transaction level to Snapshot
|
||||
ALTER DATABASE CURRENT SET MEMORY_OPTIMIZED_ELEVATE_TO_SNAPSHOT=ON ;
|
||||
```
|
||||
|
||||
## Load initial data
|
||||
|
||||
1. Within Visual Studio, open "Load Sample Data.sql"
|
||||
2. Adjust the path to the DataPoint.bcp file so it matches the location of your project and save the script.
|
||||
3. Execute the script to load each table with 4 million rows worth of sample data. This will take some time to complete.
|
||||
|
||||
## Execute the sample analytics query
|
||||
|
||||
1. Within Visual Studio, open "SampleQueries - DiskBased.sql".
|
||||
2. Execute the script to summarize the time series data stored in the disk based table.
|
||||
3. When the script completes, observe that 334 rows were returned.Take note of how long the query took to execute. Query time is shown in the bottom right of the document window in Visual Studio.
|
||||
![alt text][Disk Based Results]
|
||||
|
||||
[Disk Based Results]: Images/DiskBasedResults.png "Disk Based Results"
|
||||
4. Now, execute the script to summarize the time series data stored in the memory-optimized table, in "SampleQueries - InMemory.sql".
|
||||
When the script completes, observe that 334 rows were returned.Take note of how long the query took to execute.
|
||||
You should notice that the performance of the query against the memory-optimized table runs between 2x-10x faster than the same query, running against the same data stored in a disk based table. Query time is shown in the bottom right of the document window in Visual Studio.
|
||||
![alt text][In-Memory Results]
|
||||
|
||||
[In-Memory Results]: Images/InMemoryResults.png "In-Memory Results"
|
||||
|
||||
## Execute the queries under load
|
||||
|
||||
1. Within Visual Studio, Solution Explorer, expand the SqlLoadGenerator project and then open "App.config".
|
||||
2. Locate the connection string with the name "SqlConnection" and modify it so it points to your instance of SQL Server 2016.
|
||||
3. Save the App.config.
|
||||
4. From the Debug menu, select Start Without Debugging.
|
||||
5. At the prompt, choose option 1 to target the disk based table.
|
||||
You should see log entries when every 1000 rows are inserted.
|
||||
Leave the console running (it should run for about 3 minutes) and return to Visual Studio.
|
||||
6. Open "SampleQueries - DiskBased.sql".
|
||||
7. Execute the script to summarize the time series data stored in the disk based table.
|
||||
8. Observe that more than 334 rows were returned.Take note of how long the query took to execute.
|
||||
9. Repeat the query a few times, waiting a few seconds in between queries to get a sense of how long the query takes, even as new rows are inserted by the load generator.
|
||||
10. Close the console load generator.
|
||||
11. Run the SqlLoadGenerator again.
|
||||
This time at the prompt, choose option 2 to target the memory-optimized table.
|
||||
You should see log entries when every 1000 rows are inserted.
|
||||
12. Leave the console running (it should run for about 3 minutes) and return to Visual Studio.
|
||||
13. Open "SampleQueries - In Memory.sql".
|
||||
14. Execute the script to summarize the time series data stored in the disk based table.
|
||||
15. Observe that more than 334 rows were returned.Take note of how long the query took to execute.
|
||||
Repeat the query a few times, waiting a few seconds in between queries to get a sense of how long the query takes, even as new rows are inserted by the load generator.
|
||||
16. Close the console load generator.
|
||||
|
||||
## Conclusion
|
||||
You should observe that while neither query was affected by the heavy insert load, the query against the analytics query continued to run 2x-10x faster than the same query against the disk-based table.
|
||||
@@ -0,0 +1,86 @@
|
||||
|
||||
|
||||
|
||||
# Define the connection string
|
||||
connStr <- paste("Driver=SQL Server;Server=", "MyServer", ";Database=", "tpcx1b", ";Trusted_Connection=true;", sep = "");
|
||||
|
||||
# Input Query
|
||||
input_query <- "
|
||||
SELECT
|
||||
ss_customer_sk AS customer,
|
||||
round(CASE WHEN ((orders_count = 0) OR (returns_count IS NULL) OR (orders_count IS NULL) OR ((returns_count / orders_count) IS NULL) ) THEN 0.0 ELSE (cast(returns_count as nchar(10)) / orders_count) END, 7) AS orderRatio,
|
||||
round(CASE WHEN ((orders_items = 0) OR(returns_items IS NULL) OR (orders_items IS NULL) OR ((returns_items / orders_items) IS NULL) ) THEN 0.0 ELSE (cast(returns_items as nchar(10)) / orders_items) END, 7) AS itemsRatio,
|
||||
round(CASE WHEN ((orders_money = 0) OR (returns_money IS NULL) OR (orders_money IS NULL) OR ((returns_money / orders_money) IS NULL) ) THEN 0.0 ELSE (cast(returns_money as nchar(10)) / orders_money) END, 7) AS monetaryRatio,
|
||||
round(CASE WHEN ( returns_count IS NULL ) THEN 0.0 ELSE returns_count END, 0) AS frequency
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
ss_customer_sk,
|
||||
-- return order ratio
|
||||
COUNT(distinct(ss_ticket_number)) AS orders_count,
|
||||
-- return ss_item_sk ratio
|
||||
COUNT(ss_item_sk) AS orders_items,
|
||||
-- return monetary amount ratio
|
||||
SUM( ss_net_paid ) AS orders_money
|
||||
FROM store_sales s
|
||||
GROUP BY ss_customer_sk
|
||||
) orders
|
||||
LEFT OUTER JOIN
|
||||
(
|
||||
SELECT
|
||||
sr_customer_sk,
|
||||
-- return order ratio
|
||||
count(distinct(sr_ticket_number)) as returns_count,
|
||||
-- return ss_item_sk ratio
|
||||
COUNT(sr_item_sk) as returns_items,
|
||||
-- return monetary amount ratio
|
||||
SUM( sr_return_amt ) AS returns_money
|
||||
FROM store_returns
|
||||
GROUP BY sr_customer_sk
|
||||
) returned ON ss_customer_sk=sr_customer_sk
|
||||
"
|
||||
# Input customer data that needs to be classified
|
||||
customer_returns <- RxSqlServerData(sqlQuery = input_query,
|
||||
colClasses = c(customer = "numeric", orderRatio = "numeric", itemsRatio = "numeric", monetaryRatio = "numeric", frequency = "numeric"),
|
||||
connectionString = connStr);
|
||||
|
||||
|
||||
# Transform the data from an input dataset to an output dataset
|
||||
customer_data <- rxDataStep(customer_returns);
|
||||
#Look at the data we just loaded from SQL Server
|
||||
head(customer_data, n = 5);
|
||||
|
||||
# Determine number of clusters
|
||||
#Using a plot of the within groups sum of squares by number of clusters extracted can help determine the appropriate number of clusters.
|
||||
#We are looking for a bend in the plot. It is at this "elbow" in the plot that we have the appropriate number of clusters
|
||||
wss <- (nrow(customer_data) - 1) * sum(apply(customer_data, 2, var))
|
||||
for (i in 2:20) {
|
||||
xt = kmeans(customer_data, centers = i)
|
||||
wss[i] <- sum(kms = kmeans(customer_data, centers = i)$withinss)
|
||||
}
|
||||
plot(1:20, wss, type = "b", xlab = "Number of Clusters", ylab = "Within groups sum of squares")
|
||||
|
||||
# Output table to hold the customer group mappings
|
||||
return_cluster = RxSqlServerData(table = "return_cluster", connectionString = connStr);
|
||||
|
||||
# Set.seed for random number generator for predictability
|
||||
set.seed(10);
|
||||
|
||||
# Generate clusters using rxKmeans and output key / cluster to a table in SQL Server called return_cluster
|
||||
clust <- rxKmeans( ~ orderRatio + itemsRatio + monetaryRatio + frequency, customer_returns, numClusters = 4
|
||||
, outFile = return_cluster, outColName = "cluster", extraVarsToWrite = c("customer"), overwrite = TRUE);
|
||||
|
||||
# Read the custome returns cluster table
|
||||
customer_cluster <- rxDataStep(return_cluster);
|
||||
|
||||
#Plot the clusters (need to install library "cluster")
|
||||
#install.packages("cluster")
|
||||
library("cluster");
|
||||
clusplot(customer_data, customer_cluster$cluster, color=TRUE, shade=TRUE, labels=4, lines=0, plotchar = TRUE);
|
||||
|
||||
#Look at the clustering details and analyze results
|
||||
clust
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
USE [tpcxbb_1gb]
|
||||
|
||||
DROP PROC IF EXISTS generate_customer_return_clusters;
|
||||
GO
|
||||
CREATE procedure [dbo].[generate_customer_return_clusters]
|
||||
AS
|
||||
/*
|
||||
This procedure uses R to classify customers into different groups based on their
|
||||
purchase & return history.
|
||||
*/
|
||||
BEGIN
|
||||
DECLARE @duration FLOAT
|
||||
, @instance_name NVARCHAR(100) = @@SERVERNAME
|
||||
, @database_name NVARCHAR(128) = db_name()
|
||||
|
||||
-- Input query to generate the purchase history & return metrics
|
||||
, @input_query NVARCHAR(MAX) = N'
|
||||
SELECT
|
||||
ss_customer_sk AS customer,
|
||||
round(CASE WHEN ((orders_count = 0) OR (returns_count IS NULL) OR (orders_count IS NULL) OR ((returns_count / orders_count) IS NULL) ) THEN 0.0 ELSE (cast(returns_count as nchar(10)) / orders_count) END, 7) AS orderRatio,
|
||||
round(CASE WHEN ((orders_items = 0) OR(returns_items IS NULL) OR (orders_items IS NULL) OR ((returns_items / orders_items) IS NULL) ) THEN 0.0 ELSE (cast(returns_items as nchar(10)) / orders_items) END, 7) AS itemsRatio,
|
||||
round(CASE WHEN ((orders_money = 0) OR (returns_money IS NULL) OR (orders_money IS NULL) OR ((returns_money / orders_money) IS NULL) ) THEN 0.0 ELSE (cast(returns_money as nchar(10)) / orders_money) END, 7) AS monetaryRatio,
|
||||
round(CASE WHEN ( returns_count IS NULL ) THEN 0.0 ELSE returns_count END, 0) AS frequency
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
ss_customer_sk,
|
||||
-- return order ratio
|
||||
COUNT(distinct(ss_ticket_number)) AS orders_count,
|
||||
-- return ss_item_sk ratio
|
||||
COUNT(ss_item_sk) AS orders_items,
|
||||
-- return monetary amount ratio
|
||||
SUM( ss_net_paid ) AS orders_money
|
||||
FROM store_sales s
|
||||
GROUP BY ss_customer_sk
|
||||
) orders
|
||||
LEFT OUTER JOIN
|
||||
(
|
||||
SELECT
|
||||
sr_customer_sk,
|
||||
-- return order ratio
|
||||
count(distinct(sr_ticket_number)) as returns_count,
|
||||
-- return ss_item_sk ratio
|
||||
COUNT(sr_item_sk) as returns_items,
|
||||
-- return monetary amount ratio
|
||||
SUM( sr_return_amt ) AS returns_money
|
||||
FROM store_returns
|
||||
GROUP BY sr_customer_sk
|
||||
) returned ON ss_customer_sk=sr_customer_sk
|
||||
'
|
||||
|
||||
EXEC sp_execute_external_script
|
||||
@language = N'R'
|
||||
, @script = N'
|
||||
# Define the connection string
|
||||
connStr <- paste("Driver=SQL Server;Server=", instance_name, ";Database=", database_name, ";Trusted_Connection=true;", sep="");
|
||||
|
||||
|
||||
# Input customer data that needs to be classified. This is the result we get from our query
|
||||
customer_returns <- RxSqlServerData(sqlQuery = input_query,
|
||||
colClasses = c(customer = "numeric", orderRatio = "numeric", itemsRatio = "numeric", monetaryRatio = "numeric", frequency = "numeric"),
|
||||
connectionString = connStr);
|
||||
|
||||
# Output table to hold the customer cluster mappings
|
||||
return_cluster = RxSqlServerData(table = "customer_return_clusters", connectionString = connStr);
|
||||
|
||||
# set.seed for random number generator for predictability
|
||||
set.seed(10);
|
||||
|
||||
# generate clusters using rxKmeans and output clusters to a table called "customer_return_clusters".
|
||||
clust <- rxKmeans( ~ orderRatio + itemsRatio + monetaryRatio + frequency, customer_returns, numClusters = 4
|
||||
, outFile = return_cluster, outColName = "cluster", writeModelVars = TRUE , extraVarsToWrite = c("customer"), overwrite = TRUE);
|
||||
'
|
||||
, @input_data_1 = N''
|
||||
, @params = N'@instance_name nvarchar(100), @database_name nvarchar(128), @input_query nvarchar(max), @duration float OUTPUT'
|
||||
, @instance_name = @instance_name
|
||||
, @database_name = @database_name
|
||||
, @input_query = @input_query
|
||||
, @duration = @duration OUTPUT;
|
||||
END;
|
||||
|
||||
GO
|
||||
|
||||
|
||||
--Empty table of the results before running the stored procedure
|
||||
TRUNCATE TABLE customer_return_clusters;
|
||||
|
||||
--Execute the clustering. This will load the table customer_return_clusters with cluster mappings
|
||||
EXEC [dbo].[generate_customer_return_clusters];
|
||||
|
||||
--Now select data from table customer_return_clusters to verify that the clustering data was loaded
|
||||
SELECT * FROM customer_return_clusters;
|
||||
|
||||
--Select email addresses of customers in cluster 1
|
||||
SELECT customer.[c_email_address], customer.c_customer_sk
|
||||
FROM dbo.customer
|
||||
JOIN
|
||||
[dbo].[customer_return_clusters] as r
|
||||
ON r.customer = customer.c_customer_sk
|
||||
WHERE r.cluster = 3
|
||||
@@ -0,0 +1,70 @@
|
||||
# Perform customer clustering with SQL Server R Services
|
||||
|
||||
In this sample, we are going to get ourselves familiar with clustering.
|
||||
Clustering can be explained as organizing data into groups where members of a group are similar in some way.
|
||||
|
||||
### Contents
|
||||
|
||||
[About this sample](#about-this-sample)<br/>
|
||||
[Before you begin](#before-you-begin)<br/>
|
||||
[Sample details](#sample-details)<br/>
|
||||
[Related links](#related-links)<br/>
|
||||
|
||||
|
||||
<a name=about-this-sample></a>
|
||||
|
||||
## About this sample
|
||||
|
||||
We will be using the Kmeans algorithm to perform the clustering of customers. This can for example be used to target a specific group of customers for marketing efforts.
|
||||
Kmeans clustering is an unsupervised learning algorithm that tries to group data based on similarities. Unsupervised learning means that there is no outcome to be predicted, and the algorithm just tries to find patterns in the data.
|
||||
|
||||
In this sample, you will learn how to perform Kmeans clustering in R and deploying the solution in SQL Server 2016.
|
||||
|
||||
Follow the step by step tutorial [here](https://www.microsoft.com/en-us/sql-server/developer-get-started/rclustering) to walk through this sample.
|
||||
|
||||
<!-- Delete the ones that don't apply -->
|
||||
- **Applies to:** SQL Server 2016 (or higher)
|
||||
- **Key features:**
|
||||
- **Workload:** SQL Server R Services
|
||||
- **Programming Language:** T-SQL, R
|
||||
- **Authors:** Nellie Gustafsson
|
||||
- **Update history:** Getting started tutorial for R Services
|
||||
|
||||
<a name=before-you-begin></a>
|
||||
|
||||
## Before you begin
|
||||
|
||||
To run this sample, you need the following prerequisites.
|
||||
Section 1 in the [tutorial](https://www.microsoft.com/en-us/sql-server/developer-get-started/rclustering) covers the prerequisites.
|
||||
After that, you can download a DB backup file and restore it using Setup.sql. [Download DB](https://sqlchoice.blob.core.windows.net/sqlchoice/static/tpcxbb_1gb.bak)
|
||||
|
||||
**Software prerequisites:**
|
||||
|
||||
<!-- Examples -->
|
||||
1. SQL Server 2016 (or higher) with R Services installed
|
||||
2. SQL Server Management Studio
|
||||
3. R IDE Tool like Visual Studio
|
||||
|
||||
|
||||
<a name=sample-details></a>
|
||||
## Sample Details
|
||||
|
||||
### Customer Clustering.R
|
||||
|
||||
The R script that performs clustering.
|
||||
|
||||
### Customer Clustering.SQL
|
||||
|
||||
The SQL code to create stored procedure that performs clustering, and queries to verify and take further actions.
|
||||
|
||||
|
||||
<a name=related-links></a>
|
||||
|
||||
## Related Links
|
||||
<!-- Links to more articles. Remember to delete "en-us" from the link path. -->
|
||||
|
||||
For additional content, see these articles:
|
||||
|
||||
[SQL Server R Services - Upgrade and Installation FAQ](https://msdn.microsoft.com/en-us/library/mt653951.aspx)
|
||||
|
||||
[Other SQL Server R Services Tutorials](https://msdn.microsoft.com/en-us/library/mt591993.aspx)
|
||||
@@ -0,0 +1,13 @@
|
||||
-- Before we start, we need to restore the DB for this tutorial.
|
||||
-- Step1: Download the compressed backup file
|
||||
-- Save the file on a location where SQL Server can access it. For example: C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\Backup\
|
||||
-- In a new query window in SSMS, execute the following restore statement, but REMEMBER TO CHANGE THE FILE PATHS
|
||||
-- to match the directories of your installation!
|
||||
USE master;
|
||||
GO
|
||||
RESTORE DATABASE tpcxbb_1gb
|
||||
FROM DISK = 'C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\Backup\tpcxbb_1gb.bak'
|
||||
WITH
|
||||
MOVE 'tpcxbb_1gb' TO 'C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\DATA\tpcxbb_1gb.mdf'
|
||||
,MOVE 'tpcxbb_1gb_log' TO 'C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\DATA\tpcxbb_1gb.ldf';
|
||||
GO
|
||||
@@ -0,0 +1,54 @@
|
||||
#Connection string to connect to SQL Server
|
||||
connStr <- paste("Driver=SQL Server; Server=", "MyServer",
|
||||
";Database=", "tutorialdb", ";Trusted_Connection=true;", sep = "");
|
||||
|
||||
#Get the data from SQL Server Table
|
||||
SQL_rentaldata <- RxSqlServerData(table = "dbo.rental_data",
|
||||
connectionString = connStr, returnDataFrame = TRUE);
|
||||
|
||||
#Import the data into a data frame
|
||||
rentaldata <- rxImport(SQL_rentaldata);
|
||||
|
||||
#Let's see the structure of the data and the top rows
|
||||
head(rentaldata);
|
||||
str(rentaldata);
|
||||
|
||||
#Changing the three factor columns to factor types
|
||||
#This helps when building the model because we are explicitly saying that these values are categorical
|
||||
rentaldata$Holiday <- factor(rentaldata$Holiday);
|
||||
rentaldata$Snow <- factor(rentaldata$Snow);
|
||||
rentaldata$WeekDay <- factor(rentaldata$WeekDay);
|
||||
|
||||
#Visualize the dataset after the change
|
||||
str(rentaldata);
|
||||
|
||||
#Now let's split the dataset into 2 different sets
|
||||
#One set for training the model and the other for validating it
|
||||
train_data = rentaldata[rentaldata$Year < 2015,];
|
||||
test_data = rentaldata[rentaldata$Year == 2015,];
|
||||
|
||||
#Use this column to check the quality of the prediction against actual values
|
||||
actual_counts <- test_data$RentalCount;
|
||||
|
||||
#Model 1: Use rxLinMod to create a linear regression model. We are training the data using the training data set
|
||||
model_linmod <- rxLinMod(RentalCount ~ Month + Day + WeekDay + Snow + Holiday, data = train_data);
|
||||
|
||||
#Model 2: Use rxDTree to create a decision tree model. We are training the data using the training data set
|
||||
model_dtree <- rxDTree(RentalCount ~ Month + Day + WeekDay + Snow + Holiday, data = train_data);
|
||||
|
||||
#Use the models we just created to predict using the test data set.
|
||||
#That enables us to compare actual values of RentalCount from the two models and compare to the actual values in the test data set
|
||||
predict_linmod <- rxPredict(model_linmod, test_data, writeModelVars = TRUE);
|
||||
|
||||
predict_dtree <- rxPredict(model_dtree, test_data, writeModelVars = TRUE);
|
||||
|
||||
#Look at the top rows of the two prediction data sets.
|
||||
head(predict_linmod);
|
||||
head(predict_dtree);
|
||||
|
||||
#Now we will use the plotting functionality in R to viusalize the results from the predictions
|
||||
#We are plotting the difference between actual and predicted values for both models to compare accuracy
|
||||
par(mfrow = c(2, 1));
|
||||
plot(predict_linmod$RentalCount_Pred - predict_linmod$RentalCount, main = "Difference between actual and predicted. rxLinmod");
|
||||
plot(predict_dtree$RentalCount_Pred - predict_dtree$RentalCount, main = "Difference between actual and predicted. rxDTree");
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
--Before we start, we need to restore the DB for this tutorial.
|
||||
--Step1:Download the compressed backup file
|
||||
--Save the file on a location where SQL Server can access it. For example:C:\Program Files \Microsoft SQL Server \MSSQL13.MSSQLSERVER\MSSQL\Backup\
|
||||
--In a new query window in SSMS, execute the following restore statement, but REMEMBER TO CHANGE THE FILE PATHS
|
||||
--to match the directories of your installation!
|
||||
USE master;
|
||||
GO
|
||||
RESTORE DATABASE TutorialDB
|
||||
FROM DISK = 'C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\Backup\TutorialDB.bak'
|
||||
WITH
|
||||
MOVE 'TutorialDB' TO 'C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\DATA\TutorialDB.mdf'
|
||||
, MOVE 'TutorialDB_log' TO 'C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\DATA\TutorialDB.ldf';
|
||||
GO
|
||||
|
||||
USE tutorialdb;
|
||||
SELECT * FROM [dbo].[rental_data];
|
||||
|
||||
|
||||
-- Operationalize
|
||||
USE tutorialdb;
|
||||
GO
|
||||
-- Setup model table
|
||||
DROP TABLE IF EXISTS rental_rx_models;
|
||||
GO
|
||||
CREATE TABLE rental_rx_models (
|
||||
model_name VARCHAR(30) NOT NULL DEFAULT('default model') PRIMARY KEY,
|
||||
model VARBINARY(MAX) NOT NULL
|
||||
);
|
||||
GO
|
||||
|
||||
-- Stored procedure that trains and generates a model using the rental_data and a decision tree algorithm
|
||||
DROP PROCEDURE IF EXISTS generate_rental_rx_model;
|
||||
go
|
||||
CREATE PROCEDURE generate_rental_rx_model (@trained_model varbinary(max) OUTPUT)
|
||||
AS
|
||||
BEGIN
|
||||
EXECUTE sp_execute_external_script
|
||||
@language = N'R'
|
||||
, @script = N'
|
||||
require("RevoScaleR");
|
||||
|
||||
rental_train_data$Holiday = factor(rental_train_data$Holiday);
|
||||
rental_train_data$Snow = factor(rental_train_data$Snow);
|
||||
rental_train_data$WeekDay = factor(rental_train_data$WeekDay);
|
||||
|
||||
#Create a dtree model and train it using the training data set
|
||||
model_dtree <- rxDTree(RentalCount ~ Month + Day + WeekDay + Snow + Holiday, data = rental_train_data);
|
||||
#Before saving the model to the DB table, we need to serialize it
|
||||
trained_model <- as.raw(serialize(model_dtree, connection=NULL));'
|
||||
|
||||
, @input_data_1 = N'select "RentalCount", "Month", "Day", "WeekDay", "Snow", "Holiday" from dbo.rental_data where Year < 2015'
|
||||
, @input_data_1_name = N'rental_train_data'
|
||||
, @params = N'@trained_model varbinary(max) OUTPUT'
|
||||
, @trained_model = @trained_model OUTPUT;
|
||||
END;
|
||||
GO
|
||||
TRUNCATE TABLE rental_rx_models;
|
||||
--Script to call the stored procedure that generates the rxDTree model and save the model in a table in SQL Server
|
||||
DECLARE @model VARBINARY(MAX);
|
||||
EXEC generate_rental_rx_model @model OUTPUT;
|
||||
INSERT INTO rental_rx_models (model_name, model) VALUES('rxDTree', @model);
|
||||
SELECT * FROM rental_rx_models;
|
||||
GO
|
||||
|
||||
--Stored procedure that takes model name and new data as inout parameters and predicts the rental count for the new data
|
||||
DROP PROCEDURE IF EXISTS predict_rentals;
|
||||
GO
|
||||
CREATE PROCEDURE predict_rentals (@model VARCHAR(100),@q NVARCHAR(MAX))
|
||||
AS
|
||||
BEGIN
|
||||
DECLARE @rx_model VARBINARY(MAX) = (SELECT model FROM rental_rx_models WHERE model_name = @model);
|
||||
EXECUTE sp_execute_external_script
|
||||
@language = N'R'
|
||||
, @script = N'
|
||||
require("RevoScaleR");
|
||||
|
||||
#The InputDataSet contains the new data passed to this stored proc. We will use this data to predict.
|
||||
rentals = InputDataSet;
|
||||
|
||||
#Convert types to factors
|
||||
rentals$Holiday = factor(rentals$Holiday);
|
||||
rentals$Snow = factor(rentals$Snow);
|
||||
rentals$WeekDay = factor(rentals$WeekDay);
|
||||
|
||||
#Before using the model to predict, we need to unserialize it
|
||||
rental_model = unserialize(rx_model);
|
||||
|
||||
#Call prediction function
|
||||
rental_predictions = rxPredict(rental_model, rentals);'
|
||||
, @input_data_1 = @q
|
||||
, @output_data_1_name = N'rental_predictions'
|
||||
, @params = N'@rx_model varbinary(max)'
|
||||
, @rx_model = @rx_model
|
||||
WITH RESULT SETS (("RentalCount_Predicted" FLOAT));
|
||||
|
||||
END;
|
||||
GO
|
||||
|
||||
--Execute the predict_rentals stored proc and pass the modelname and a query string with a set of features we want to use to predict the rental count
|
||||
EXEC dbo.predict_rentals @model = 'rxDTree',
|
||||
@q ='SELECT CONVERT(INT, 3) AS Month, CONVERT(INT, 24) AS Day, CONVERT(INT, 4) AS WeekDay, CONVERT(INT, 1) AS Snow, CONVERT(INT, 1) AS Holiday';
|
||||
GO
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
# Build a predictive model with SQL Server R Services
|
||||
|
||||
This sample shows how to create a predictive model in R and operationalize it with SQL Server 2016.
|
||||
|
||||
### Contents
|
||||
|
||||
[About this sample](#about-this-sample)<br/>
|
||||
[Before you begin](#before-you-begin)<br/>
|
||||
[Sample details](#sample-details)<br/>
|
||||
[Related links](#related-links)<br/>
|
||||
|
||||
|
||||
<a name=about-this-sample></a>
|
||||
|
||||
## About this sample
|
||||
|
||||
Predictive modeling is a powerful way to add intelligence to your application. It enables applications to predict outcomes against new data.
|
||||
The act of incorporating predictive analytics into your applications involves two major phases:
|
||||
model training and model operationalization.
|
||||
|
||||
In this sample, you will learn how to create a predictive model in R and operationalize it with SQL Server 2016.
|
||||
|
||||
Follow the step by step tutorial [here](http://aka.ms/sqldev/R) to walk through this sample.
|
||||
|
||||
<!-- Delete the ones that don't apply -->
|
||||
- **Applies to:** SQL Server 2016 (or higher)
|
||||
- **Key features:**
|
||||
- **Workload:** SQL Server R Services
|
||||
- **Programming Language:** T-SQL, R
|
||||
- **Authors:** Nellie Gustafsson
|
||||
- **Update history:** Getting started tutorial for R Services
|
||||
|
||||
<a name=before-you-begin></a>
|
||||
|
||||
## Before you begin
|
||||
|
||||
To run this sample, you need the following prerequisites.
|
||||
Section 1 in the [tutorial](http://aka.ms/sqldev/R) covers the prerequisites.
|
||||
After that, you can download a DB backup file and restore it using Setup.sql. [Download DB](https://deve2e.azureedge.net/sqlchoice/static/TutorialDB.bak)
|
||||
|
||||
**Software prerequisites:**
|
||||
|
||||
<!-- Examples -->
|
||||
1. SQL Server 2016 (or higher) with R Services installed
|
||||
2. SQL Server Management Studio
|
||||
3. R IDE Tool like Visual Studio
|
||||
|
||||
|
||||
<a name=sample-details></a>
|
||||
## Sample Details
|
||||
|
||||
### PredictiveModel.R
|
||||
|
||||
The R script that generates a predictive model and uses it to predict rental counts
|
||||
|
||||
### PredictiveModel.SQL
|
||||
|
||||
Takes the R code in PredictiveModel.R and uses it inside SQL Server. Creating stored procedures for training and prediction.
|
||||
|
||||
|
||||
|
||||
<a name=related-links></a>
|
||||
|
||||
## Related Links
|
||||
<!-- Links to more articles. Remember to delete "en-us" from the link path. -->
|
||||
|
||||
For additional content, see these articles:
|
||||
|
||||
[SQL Server R Services - Upgrade and Installation FAQ](https://msdn.microsoft.com/en-us/library/mt653951.aspx)
|
||||
|
||||
[Other SQL Server R Services Tutorials](https://msdn.microsoft.com/en-us/library/mt591993.aspx)
|
||||
@@ -0,0 +1,13 @@
|
||||
-- Before we start, we need to restore the DB for this tutorial.
|
||||
-- Step1: Download the compressed backup file (https://deve2e.azureedge.net/sqlchoice/static/TutorialDB.bak)
|
||||
--Save the file on a location where SQL Server can access it. For example: C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\Backup\
|
||||
-- In a new query window in SSMS, execute the following restore statement, but REMEMBER TO CHANGE THE FILE PATHS
|
||||
-- to match the directories of your installation!
|
||||
USE master;
|
||||
GO
|
||||
RESTORE DATABASE TutorialDB
|
||||
FROM DISK = 'C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\Backup\TutorialDB.bak'
|
||||
WITH
|
||||
MOVE 'TutorialDB' TO 'C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\DATA\TutorialDB.mdf'
|
||||
,MOVE 'TutorialDB_log' TO 'C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\DATA\TutorialDB.ldf';
|
||||
GO
|
||||
@@ -409,7 +409,7 @@ GO
|
||||
SET QUOTED_IDENTIFIER ON
|
||||
GO
|
||||
|
||||
ALTER PROCEDURE [dbo].[PerformETL]
|
||||
CREATE PROCEDURE [dbo].[PerformETL]
|
||||
AS
|
||||
BEGIN
|
||||
INSERT INTO [dbo].[LoanStats]
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
# Samples for SQL Server R Services
|
||||
|
||||
[Implementing Predictive Analytics](Implementing Predictive Analytics)
|
||||
Go to the getting started tutorials to learn more about:
|
||||
|
||||
[Predictive Modeling with R Services](https://www.microsoft.com/en-us/sql-server/developer-get-started/rprediction)
|
||||
|
||||
[Customer Clustering with R Services](https://www.microsoft.com/en-us/sql-server/developer-get-started/rclustering)
|
||||
|
||||
Step-by-step sample that explains the basics about predictive analytics for developers. The lab will take you about 15 min and will show you how to create an application that uses node.js and SQL Server 2016 to predict if a cab driver will be tipped or not.
|
||||
|
||||
[Telco Customer Churn](Telco Customer Churn)
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 211 KiB |
|
Before Width: | Height: | Size: 69 KiB |
@@ -1,83 +0,0 @@
|
||||
# Clustering in R (SQL Server 2016)
|
||||
In this code snack, developers will experience authoring R code to help them run a clustering exercise that “magically” groups data into distinct populations by using an unsupervised clustering algorithm, k-means. The k-means script will be packaged within a SQL stored procedure for convenient execution from a .NET application.
|
||||
|
||||
## About Clustering
|
||||
The goal of a clustering algorithm is to look at an input set of data and attempt to identify groups of data by virtue of the similarity between the features of each example in the data set. What makes clustering algorithms particularly powerful is that they do not need a training step like the other algorithms— you simply provide them the data, tell them how many clusters you want to create and they assign each example to a group. The canonical clustering algorithm is k-means.
|
||||
|
||||
## Requirements
|
||||
- Visual Studio 2015 with Update 3 (or later)
|
||||
- SQL Server 2016 Developer Edition (or higher)
|
||||
|
||||
## Required SQL Server Configuration
|
||||
- Make sure that your installation of SQL Server includes R Services, see [https://msdn.microsoft.com/en-us/library/mt696069.aspx](https://msdn.microsoft.com/en-us/library/mt696069.aspx)
|
||||
- Using SQL Server Configuration Manager (which is launched from the Start menu), make sure that TCP/IP connections are enabled to your instance of SQL Server (under SQL Server Network Configuration).
|
||||
![alt text][SQL Config]
|
||||
|
||||
[SQL Config]: Images/SqlConfig.png "SQL Server Network Configuration"
|
||||
- Be sure that the SQL Server, SQL Server Launchpad and SQL Server Browser services are all running.
|
||||
|
||||
## Clone the provided project
|
||||
Clone this repo on to your local machine.
|
||||
The recommended path is C:\Clustering in R
|
||||
|
||||
## Create the database and tables
|
||||
1. Open the ClusteringConsole.sln solution using Visual Studio 2015
|
||||
2. From Solution Explorer, expand the ClusteringConsole solution, then Solution Items folder and open “Create Sample Database.sql”.
|
||||
3. Adjust the file path for the FROM clause in the BULK INSERT statement if you cloned the project to a different location.
|
||||
4. Select the Execute button
|
||||
5. In the Connect dialog, provide your server name, authentication mode, username and password (as appropriate).
|
||||
6. Wait for the script to complete successfully.
|
||||
|
||||
## Create the Clustering Stored Procedure
|
||||
1. Within Visual Studio, open “Create Procedure ClusterTaxiData.sql”.
|
||||
This stored procedure queries the data in the nyctaxi_features table and creates four clusters of data based on the passenger_count (the number of passengers in the taxi cab) and direct_distance (the distance traveled, measured as the crow flies). It uses the rxKmeans method to accomplish this, which runs the K-Means algorithm to group the data into the configured number of clusters (four clusters in this case). The formula syntax "~ passenger_count + direct_distance” used in the first parameter simply means to cluster around those two columns from the input data.
|
||||
```
|
||||
CREATE PROCEDURE [dbo].[ClusterTaxiData]
|
||||
AS
|
||||
BEGIN
|
||||
DECLARE @inquery nvarchar(max) = N'
|
||||
select tipped, passenger_count, trip_time_in_secs, trip_distance, direct_distance
|
||||
from nyctaxi_features
|
||||
'
|
||||
|
||||
EXEC sp_execute_external_script
|
||||
@language = N'R',
|
||||
@script = N'
|
||||
|
||||
## Cluster the data
|
||||
clusters <- rxKmeans(~ passenger_count + direct_distance, data = InputDataSet, numClusters = 4, algorithm = "lloyd")
|
||||
|
||||
## Return the result (by convention the result data set is retrieved from a variable named OutputDataSet).
|
||||
OutputDataSet <- as.data.frame(clusters$centers) ;
|
||||
|
||||
',
|
||||
@input_data_1 = @inquery
|
||||
WITH RESULT SETS ((passenger_count real, direct_distance real))
|
||||
;
|
||||
END
|
||||
GO
|
||||
```
|
||||
2. Execute the script to create the stored procedure.
|
||||
|
||||
## Execute the Clustering Stored Procedure
|
||||
1. Within Visual Studio, open “Execute Procedure ClusterTaxiData.sql”.
|
||||
2. Select the Execute button
|
||||
3. In the Connect dialog, provide your server name, authentication mode, username and password (as appropriate).
|
||||
4. Wait for the script to complete successfully.
|
||||
5. Observe the results, you should have four clusters of data, each a row in the results. You might interpret these results in order as short trips with one passenger, long trips with two passengers, moderate trips with two passengers and short trips with lots of passengers.
|
||||
|
||||
![alt text][Clustering Results]
|
||||
|
||||
[Clustering Results]: Images/ClusteringResults.png "Clustering Results"
|
||||
|
||||
## Leverage Clustering from an Application
|
||||
1. Within Visual Studio, open app.config located underneath the SqlSecurity project in Solution Explorer.
|
||||
2. Set the connectionString value so that it points to your SQL Server.
|
||||
3. Save the file.
|
||||
4. From the Debug menu, select Start Without Debugging.
|
||||
|
||||
Observe the clusters for the taxi rides as retrieved by the application, you have now integrated machine learning into your console application!
|
||||
|
||||
![alt text][Application Results]
|
||||
|
||||
[Application Results]: Images/ApplicationResults.png "Application Results"
|
||||
@@ -1 +0,0 @@
|
||||
Coming soon
|
||||
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 30 KiB |
@@ -1,203 +0,0 @@
|
||||
# Dynamic Data Masking & Row Level Security Code Snack
|
||||
In this code snack, developers will create a database having human resources data, including a table containing simulated employee pay data. They will be guided thru the sample data to highlight the sensitive information it contains (e.g., social security numbers and salaries) and then configuring the masking of the sensitive data. In addition, they will enable Row Level Security to handle three different roles: contractors (who have no visibility to any rows except their own in the table), HR (who can view all employee rows except those of executives) and Executives (who can view all employee rows). They will complete a node.js application that queries the database to see the differing outcomes that result based on Row Level Security policy.
|
||||
|
||||
## Requirements
|
||||
- [Visual Studio Code](https://code.visualstudio.com/download)
|
||||
- [SQL Server on Linux](https://www.microsoft.com/en-us/sql-server/sql-server-on-linux)
|
||||
- [Node.js](https://nodejs.org/en/download/)
|
||||
- [MSSQL extension for VS Code](https://marketplace.visualstudio.com/items?itemName=sanagama.vscode-mssql)
|
||||
|
||||
|
||||
## Clone the provided project
|
||||
Clone this repo on to your local machine.
|
||||
|
||||
## Configure your connection to SQL on Linux
|
||||
1. Launch Visual Studio Code and open the project folder in Visual Studio Code.
|
||||
2. From Code, Preferences select User Settings.
|
||||
3. In between the curly braces, paste the following which adds two connection to SQL Server on Linux that the MSSQL extension will utilize:
|
||||
```
|
||||
"vscode-mssql.connections":
|
||||
[
|
||||
{
|
||||
// connection 1
|
||||
// All required inputs are present. No prompts when you choose this connection from the picklist.
|
||||
"server": "localhost",
|
||||
"database": "master",
|
||||
"user": "sa",
|
||||
"password": "Abc1234567890"
|
||||
},
|
||||
{
|
||||
// connection 2
|
||||
// All required inputs are present. No prompts when you choose this connection from the picklist.
|
||||
"server": "localhost",
|
||||
"database": "AdventureWorks",
|
||||
"user": "Contractor",
|
||||
"password": "Abc!1234"
|
||||
}
|
||||
]
|
||||
|
||||
```
|
||||
4. Update the server for both of the above entries to match your SQL on Linux server.
|
||||
5. Update the password for the first entry (sa user) so it is set to the value used by your SA user.
|
||||
6. Leave the password as is for the second connection (contractor user). You will create and use this user later in the steps.
|
||||
|
||||
|
||||
## Create the database and tables
|
||||
1. Launch Visual Studio Code and open the project folder in Visual Studio Code.
|
||||
2. Open “Create Sample Database.sql” underneath the SQL Resources folder.
|
||||
3. Bring up the Command Palette (cmd+shift+P on Mac)
|
||||
4. Type mssql
|
||||
|
||||
![alt text][mssqlcmd]
|
||||
|
||||
[mssqlcmd]: Images/mssqlcommand.png "mssql command"
|
||||
|
||||
5. Choose Connect to database
|
||||
6. Choose the localhost using your SA user
|
||||
|
||||
![alt text][mssqlcmd3]
|
||||
|
||||
[mssqlcmd3]: Images/mssqlchoose.png "mssql choose connection"
|
||||
|
||||
7. Back in the document editor for Create Sample Database.sql, make sure you have nothing highlighted and execute it with cmd+shift+e
|
||||
8. Wait for the script to complete successfully.
|
||||
|
||||
|
||||
## Explore the Sample Data
|
||||
1. Within Visual Studio Code, open “Explore Data.sql"
|
||||
2. Execute the script to observe the sensitive fields in this query that summarize the pay rate for employees: NationalIDNumber (e.g., social security number) and Rate (e.g., pay rate)
|
||||
3. Notice the employee table has the NationalIDNumber which is sensitive field and the EmployePayHistory table has the Rate field which is sensitive because it captures the employees rate of pay.
|
||||
|
||||
![alt text][Explore Data]
|
||||
|
||||
[Explore Data]: Images/mssqlunmaskedresults.png "Explore Data"
|
||||
|
||||
4. Click the X to close the MSSQL Output tab. In the steps that follow, remember to close this anytime you will execute a new query or you may not see the results of your latest query.
|
||||
|
||||
|
||||
## Configure Masking
|
||||
1. Within Visual Studio Code, open “Configure Masking.sql"
|
||||
2. Execute the script to create mask both the NationalIDNumber and Rate fields.
|
||||
```
|
||||
-- Mask the NationalIDNumber column so it only displays the last two digits of field (for example: XX-XXX-XX43)
|
||||
ALTER TABLE HumanResources.Employee
|
||||
ALTER COLUMN NationalIDNumber ADD MASKED WITH(FUNCTION = 'partial(0,"XX-XXX-XX",2)')
|
||||
|
||||
|
||||
-- Mask the rate by providing a random value in place of the actual rate
|
||||
ALTER TABLE HumanResources.EmployeePayHistory
|
||||
ALTER COLUMN Rate ADD MASKED WITH (FUNCTION = 'random(20,150)')
|
||||
```
|
||||
3. Return to “Explore Data.sql” and execute this script again.
|
||||
4. Observe that even though you enabled masking on the table, these fields are still available to you (the administrative user) in their original unmasked format.
|
||||
5. To view the results with the masks applied, create a new user who can query from the database who does not have priveleges to see the unmasked data (in other words, they will always see the masked data).
|
||||
6. Open “Create Contractor User.sql” and execute it to create a new login and user with the name Contractor and password Abc!1234.
|
||||
7. Return to “Explore Data.sql” and execute this script again.
|
||||
8. Bring up the Command Palette (cmd+shift+P on Mac)
|
||||
9. Type mssql
|
||||
10. Choose Connect to database
|
||||
11. Choose the localhost using your Contractor user
|
||||
12. Execute this script again.
|
||||
13. Observe that now the NationalIDNumber only displays the last two digits, and the Rate values are different from before.
|
||||
![alt text][Masked Data]
|
||||
|
||||
[Masked Data]: Images/mssqlmaskedresults.png "Masked Data"
|
||||
|
||||
## Configure Row Level Security
|
||||
1. Next, consider the scenario where you want to enforce a policy where only Executive users in the organization can see all employee rows in the Employee table. Users in the Human resources department can see all rows except those of the executives. Finally, all other users can only see their row.
|
||||
2. This is something you can accomplish using Row Level Security in a fashion that “just works” and applies transparently to the user issuing the query.
|
||||
3. Within Visual Studio Code, change your localhost connection back to use the SA user.
|
||||
4. Open “Configure Row Level Security.sql”.
|
||||
5. Execute the script. This will create a schema (to hold our security related functions), a predicate function that filters the rows based upon the user performing the querying, and a policy that is applied to the Employee table that uses the predicate function to filter the result set to only the rows the user should be seeing.
|
||||
```
|
||||
-- Best practice, create a schema to hold security predicates
|
||||
CREATE SCHEMA Security;
|
||||
GO
|
||||
|
||||
|
||||
-- Create the predicate function
|
||||
CREATE FUNCTION Security.LimitAccess(@LoginID nvarchar(256), @OrganizationLevel smallint)
|
||||
RETURNS TABLE
|
||||
WITH SCHEMABINDING
|
||||
AS
|
||||
RETURN SELECT 1 as LimitAccess_Result
|
||||
FROM HumanResources.EmployeeDepartmentHistory deptHist INNER JOIN HumanResources.Employee emp
|
||||
ON deptHist.BusinessEntityID = emp.BusinessEntityID
|
||||
WHERE (emp.LoginID = CAST(SESSION_CONTEXT(N'LoginID') AS nvarchar(256)) AND EndDate is NULL AND DepartmentID = 9 AND @OrganizationLevel > 1) OR
|
||||
(emp.LoginID = CAST(SESSION_CONTEXT(N'LoginID') AS nvarchar(256)) AND EndDate is NULL AND DepartmentID = 16) OR
|
||||
CAST(SESSION_CONTEXT(N'LoginID') AS nvarchar(256)) = @LoginID;
|
||||
GO
|
||||
|
||||
|
||||
-- Create a policy that applies the predicate
|
||||
CREATE SECURITY POLICY Security.HumanResourcesPolicy
|
||||
ADD FILTER PREDICATE Security.LimitAccess(LoginID, OrganizationLevel) ON HumanResources.Employee
|
||||
WITH (STATE = ON);
|
||||
GO
|
||||
```
|
||||
6. Now, open “Explore Data with RLS.sql”. This query will show the differing result sets that appear for different users when the policy is in action. Notice that this query does not rely on the credentials used to connect to SQL Server, but rather the login store in the session context.
|
||||
7. This is a useful pattern when you have an application that uses one connection string to SQL Server, but is operating in an environment where your application handles the login, and that login is different from the credentials used to access SQL Server. You can uses this application login information to inform the Row Level Security policy.
|
||||
```
|
||||
-- SET the context to Contractor user and make it immutable for the duration of the connection
|
||||
EXEC sp_set_session_context @key=N'LoginID', @value=N'adventure-works\lynn0';
|
||||
|
||||
-- Query the table as usual: observe that only the one row is returned
|
||||
SELECT * FROM HumanResources.Employee;
|
||||
```
|
||||
8. Execute the query. Observe the different result sets that appear for the exact same query— they are made different only because of the LoginID session context provided.
|
||||
|
||||
![alt text][RLS Data]
|
||||
|
||||
[RLS Data]: Images/rlsresults.png "RLS Data"
|
||||
|
||||
|
||||
## Leverage Row Level Security from an Application
|
||||
1. Let’s put Row Level Security to work within the context of an application, in this case a node.js application.
|
||||
2. Within Visual Studio Code, open SqlClient.js located underneath the SqlSecurity folder root.
|
||||
3. Near the top, modify the values of the config element so that they contain the appropriate values to connect to your instance of the database.
|
||||
```
|
||||
// Provide the connection details appropriate to your environment
|
||||
var config = {
|
||||
userName: 'sa',
|
||||
password: 'Abc1234567890',
|
||||
server: 'localhost',
|
||||
options: {
|
||||
database: 'adventureworks',
|
||||
encrypt: true
|
||||
},
|
||||
loginID : 'adventure-works\\ken0'
|
||||
};
|
||||
```
|
||||
4. Save the file.
|
||||
5. Open an instance of Terminal and navigate to the directory that contains SqlClient.js.
|
||||
6. Execute the following to install the tediuos package:
|
||||
```
|
||||
npm install tedious
|
||||
```
|
||||
6. Now run the node.js app:
|
||||
```
|
||||
node SqlClient.js
|
||||
```
|
||||
7. Observe the query that is run and that only 1 row is returned (the row for that user in the employee table).
|
||||
```
|
||||
$ node SqlClient.js
|
||||
Connected.
|
||||
Executing query: SELECT Count(*) FROM [HumanResources].[Employee]
|
||||
= 1
|
||||
1 rows
|
||||
```
|
||||
8. Experiment with the other users to see the differing query counts that are returned. In SqlClient.js modify the config object, loginID value to either 'adventure-works\\paula0' or 'adventure-works\\ken0'.
|
||||
```
|
||||
var config = {
|
||||
userName: 'sa',
|
||||
password: 'Abc1234567890',
|
||||
server: 'localhost',
|
||||
options: {
|
||||
database: 'adventureworks',
|
||||
encrypt: true
|
||||
},
|
||||
loginID : 'adventure-works\\paula0'
|
||||
};
|
||||
```
|
||||
8. Run the node application again as previously shown.
|
||||
9. Observe that the same query is run as before, but either 283 rows or 290 row are returned depending on the loginID used.
|
||||
@@ -1,11 +0,0 @@
|
||||
USE [AdventureWorks]
|
||||
GO
|
||||
|
||||
-- Mask the NationalIDNumber column so it only displays the last two digits of field (for example: XX-XXX-XX43)
|
||||
ALTER TABLE HumanResources.Employee
|
||||
ALTER COLUMN NationalIDNumber ADD MASKED WITH(FUNCTION = 'partial(0,"XX-XXX-XX",2)')
|
||||
|
||||
|
||||
-- Mask the rate by providing a random value in place of the actual rate
|
||||
ALTER TABLE HumanResources.EmployeePayHistory
|
||||
ALTER COLUMN Rate ADD MASKED WITH (FUNCTION = 'random(20,150)')
|
||||
@@ -1,27 +0,0 @@
|
||||
USE [AdventureWorks]
|
||||
GO
|
||||
|
||||
-- Best practice, create a schema to hold security predicates
|
||||
CREATE SCHEMA Security;
|
||||
GO
|
||||
|
||||
|
||||
-- Create the predicate function
|
||||
CREATE FUNCTION Security.LimitAccess(@LoginID nvarchar(256), @OrganizationLevel smallint)
|
||||
RETURNS TABLE
|
||||
WITH SCHEMABINDING
|
||||
AS
|
||||
RETURN SELECT 1 as LimitAccess_Result
|
||||
FROM HumanResources.EmployeeDepartmentHistory deptHist INNER JOIN HumanResources.Employee emp
|
||||
ON deptHist.BusinessEntityID = emp.BusinessEntityID
|
||||
WHERE (emp.LoginID = CAST(SESSION_CONTEXT(N'LoginID') AS nvarchar(256)) AND EndDate is NULL AND DepartmentID = 9 AND @OrganizationLevel > 1) OR
|
||||
(emp.LoginID = CAST(SESSION_CONTEXT(N'LoginID') AS nvarchar(256)) AND EndDate is NULL AND DepartmentID = 16) OR
|
||||
CAST(SESSION_CONTEXT(N'LoginID') AS nvarchar(256)) = @LoginID;
|
||||
GO
|
||||
|
||||
|
||||
-- Create a policy that applies the predicate
|
||||
CREATE SECURITY POLICY Security.HumanResourcesPolicy
|
||||
ADD FILTER PREDICATE Security.LimitAccess(LoginID, OrganizationLevel) ON HumanResources.Employee
|
||||
WITH (STATE = ON);
|
||||
GO
|
||||
@@ -1,19 +0,0 @@
|
||||
USE [master]
|
||||
GO
|
||||
|
||||
CREATE LOGIN [Contractor] WITH PASSWORD=N'Abc!1234',
|
||||
DEFAULT_DATABASE=[AdventureWorks],
|
||||
DEFAULT_LANGUAGE=[us_english],
|
||||
CHECK_EXPIRATION=OFF,
|
||||
CHECK_POLICY=OFF
|
||||
GO
|
||||
|
||||
USE [AdventureWorks]
|
||||
GO
|
||||
|
||||
CREATE USER [Contractor] FOR LOGIN [Contractor]
|
||||
WITH DEFAULT_SCHEMA=[dbo]
|
||||
GO
|
||||
|
||||
ALTER ROLE [db_datareader] ADD MEMBER [Contractor]
|
||||
GO
|
||||
@@ -1,6 +0,0 @@
|
||||
|
||||
ALTER DATABASE AdventureWorks
|
||||
SET SINGLE_USER --or RESTRICTED_USER
|
||||
WITH ROLLBACK IMMEDIATE;
|
||||
GO
|
||||
DROP DATABASE AdventureWorks;
|
||||
@@ -1,29 +0,0 @@
|
||||
USE [AdventureWorks]
|
||||
GO
|
||||
|
||||
-- Query the table as usual: observe that no rows are returned because no login ID context is set
|
||||
SELECT * FROM HumanResources.Employee;
|
||||
|
||||
|
||||
|
||||
-- SET the context to Contractor user and make it immutable for the duration of the connection
|
||||
EXEC sp_set_session_context @key=N'LoginID', @value=N'adventure-works\lynn0';
|
||||
|
||||
-- Query the table as usual: observe that only the one row is returned
|
||||
SELECT * FROM HumanResources.Employee;
|
||||
|
||||
|
||||
|
||||
-- SET the context to an HR user
|
||||
EXEC sp_set_session_context @key=N'LoginID', @value=N'adventure-works\paula0';
|
||||
|
||||
-- Query the table as usual: observe that only rows with OrganizationLevel of 2 or greater are returned
|
||||
SELECT * FROM HumanResources.Employee;
|
||||
|
||||
|
||||
|
||||
-- SET the context to an Executive user
|
||||
EXEC sp_set_session_context @key=N'LoginID', @value=N'adventure-works\ken0';
|
||||
|
||||
-- Query the table as usual: observe that all rows are returned
|
||||
SELECT * FROM HumanResources.Employee;
|
||||
@@ -1,7 +0,0 @@
|
||||
USE [AdventureWorks]
|
||||
GO
|
||||
|
||||
-- Observe the sensitive fields in this query that summarize the pay rate for employees: NationalIDNumber, Rate
|
||||
SELECT TOP 10 emp.BusinessEntityID, NationalIDNumber, JobTitle, RateChangeDate, Rate
|
||||
FROM HumanResources.Employee emp INNER JOIN HumanResources.EmployeePayHistory pay
|
||||
ON emp.BusinessEntityID = pay.BusinessEntityId;
|
||||
@@ -1,87 +0,0 @@
|
||||
var Connection = require('tedious').Connection;
|
||||
var Request = require('tedious').Request;
|
||||
var TYPES = require('tedious').TYPES;
|
||||
|
||||
// Provide the connection details appropriate to your environment
|
||||
var config = {
|
||||
userName: 'sa',
|
||||
password: 'Abc1234567890',
|
||||
server: 'localhost',
|
||||
options: {
|
||||
database: 'adventureworks',
|
||||
encrypt: true
|
||||
},
|
||||
loginID : 'adventure-works\\lynn0'
|
||||
};
|
||||
|
||||
var connection = new Connection(config);
|
||||
|
||||
connection.on('connect', function(err) {
|
||||
|
||||
if (err)
|
||||
{
|
||||
console.log("Unable to Connect: " + err);
|
||||
return;
|
||||
}
|
||||
|
||||
// If no error, then good to go...
|
||||
console.log("Connected.");
|
||||
|
||||
executeSetSessionStatement();
|
||||
|
||||
});
|
||||
|
||||
|
||||
function executeSetSessionStatement() {
|
||||
// Specify the name of the predictive stored procedure
|
||||
storedProcedureName = "sp_set_session_context";
|
||||
|
||||
request = new Request(storedProcedureName, function(err, rowCount) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
} else {
|
||||
// Invoke the query in the session context
|
||||
executeQuery();
|
||||
}
|
||||
});
|
||||
|
||||
// The input values to the prediction are provided here:
|
||||
request.addParameter('@key', TYPES.VarChar, 'LoginID');
|
||||
request.addParameter('@value', TYPES.VarChar, config.loginID);
|
||||
request.addParameter('@readonly', TYPES.Int, '1');
|
||||
|
||||
// Iterate over any received rows in the result
|
||||
request.on('row', function(columns) {
|
||||
console.log("Session login set")
|
||||
columns.forEach(function(column) {
|
||||
console.log(column.metadata.colName + " = " + column.value);
|
||||
});
|
||||
});
|
||||
|
||||
connection.callProcedure(request);
|
||||
}
|
||||
|
||||
function executeQuery() {
|
||||
|
||||
// Specify the name of the predictive stored procedure
|
||||
sqlQuery = "SELECT Count(*) FROM [HumanResources].[Employee]";
|
||||
|
||||
request = new Request(sqlQuery, function(err, rowCount) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
} else {
|
||||
console.log(rowCount + ' rows');
|
||||
}
|
||||
});
|
||||
|
||||
// Iterate over any received rows in the result
|
||||
request.on('row', function(columns) {
|
||||
columns.forEach(function(column) {
|
||||
console.log(column.metadata.colName + " = " + column.value);
|
||||
});
|
||||
});
|
||||
|
||||
console.log("Executing query: " + sqlQuery);
|
||||
|
||||
connection.execSql(request);
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2010-2011 Mike D Pilsbury
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -1,52 +0,0 @@
|
||||
# Tedious (node implementation of TDS)
|
||||
[](https://david-dm.org/pekim/tedious) [](http://badge.fury.io/js/tedious) [](http://travis-ci.org/pekim/tedious) [](https://ci.appveyor.com/project/pekim/tedious)
|
||||
|
||||
Tedious is an implementation of the [TDS protocol](http://msdn.microsoft.com/en-us/library/dd304523.aspx),
|
||||
which is used to interact with instances of Microsoft's SQL Server. It is intended to be a fairly slim implementation of the protocol, with not too much additional functionality.
|
||||
|
||||
**NOTE: New columns are nullable by default as of version 1.11.0**
|
||||
|
||||
Previous behavior can be restored using `config.options.enableAnsiNullDefault = false`. See [pull request 230](https://github.com/pekim/tedious/pull/230).
|
||||
|
||||
**NOTE: Default login behavior has changed slightly as of version 1.2**
|
||||
|
||||
See the [changelog](http://pekim.github.io/tedious/changelog.html) for version history.
|
||||
|
||||
|
||||
### Supported TDS versions
|
||||
|
||||
- TDS 7.4 (SQL Server 2012/2014)
|
||||
- TDS 7.3.B (SQL Server 2008 R2)
|
||||
- TDS 7.3.A (SQL Server 2008)
|
||||
- TDS 7.2 (SQL Server 2005)
|
||||
- TDS 7.1 (SQL Server 2000)
|
||||
|
||||
## Installation
|
||||
|
||||
npm install tedious
|
||||
|
||||
|
||||
<a name="documentation" />
|
||||
## Documentation
|
||||
More documentation is available at [pekim.github.io/tedious/](http://pekim.github.io/tedious/)
|
||||
|
||||
<a name="discussion" />
|
||||
## Discussion
|
||||
Google Group - http://groups.google.com/group/node-tedious
|
||||
|
||||
<a name="name" />
|
||||
## Name
|
||||
_Tedious_ is simply derived from a fast, slightly garbled, pronunciation of the letters T, D and S.
|
||||
|
||||
<a name="license" />
|
||||
## Licence
|
||||
|
||||
Copyright (c) 2010-2014 Mike D Pilsbury
|
||||
|
||||
The MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -1,52 +0,0 @@
|
||||
version: "{build}"
|
||||
|
||||
environment:
|
||||
matrix:
|
||||
- nodejs_version: "0.10"
|
||||
- nodejs_version: "0.12"
|
||||
- nodejs_version: "4"
|
||||
- nodejs_version: "5"
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /^maint\/.+/
|
||||
- /v\d+\.\d+\.\d+/
|
||||
|
||||
install:
|
||||
- ps: Install-Product node $env:nodejs_version
|
||||
- npm install
|
||||
|
||||
cache:
|
||||
- node_modules
|
||||
|
||||
build: off
|
||||
|
||||
before_test:
|
||||
- sc config sqlbrowser start= auto
|
||||
- net start sqlbrowser
|
||||
|
||||
- mkdir C:\Users\appveyor\.tedious
|
||||
|
||||
test_script:
|
||||
- node --version
|
||||
- npm --version
|
||||
|
||||
- cmd: |
|
||||
SET EXITVAL=0
|
||||
|
||||
npm run-script test || SET EXITVAL=1
|
||||
|
||||
powershell %cd%\scripts\appveyor\config.ps1 SQL2008R2SP2
|
||||
npm run-script test-integration || SET EXITVAL=1
|
||||
net stop MSSQL$SQL2008R2SP2
|
||||
|
||||
powershell %cd%\scripts\appveyor\config.ps1 SQL2012SP1
|
||||
npm run-script test-integration || SET EXITVAL=1
|
||||
net stop MSSQL$SQL2012SP1
|
||||
|
||||
powershell %cd%\scripts\appveyor\config.ps1 SQL2014
|
||||
npm run-script test-integration || SET EXITVAL=1
|
||||
net stop MSSQL$SQL2014
|
||||
|
||||
EXIT /B %EXITVAL%
|
||||
@@ -1,33 +0,0 @@
|
||||
# Tedious Benchmarks
|
||||
|
||||
This folder contains a collection of benchmarks for `tedious`.
|
||||
|
||||
Running all existing benchmarks is easy, just execute the following from
|
||||
inside the `tedious` root folder:
|
||||
|
||||
```sh
|
||||
node benchmarks
|
||||
```
|
||||
|
||||
**NOTE:** The benchmarks try to load `tedious` code from `lib`, so make sure
|
||||
you run `npm run prepublish` first.
|
||||
|
||||
This will serially execute every available benchmark test in a
|
||||
seperate Node.js process. Running each benchmark in a separate process
|
||||
ensures that each benchmark is run with a clean slate.
|
||||
|
||||
You can also execute a specific benchmarks:
|
||||
|
||||
```sh
|
||||
node benchmarks/<type>/<benchmark-name>
|
||||
```
|
||||
|
||||
The benchmarks are executed by using the `benchmark` module. Unfortunately,
|
||||
this module can add a bit of useless noise when trying to collect profiling
|
||||
information. To reduce this noise, benchmarks can be run in a special "profile"
|
||||
mode. This will execute the benchmark's code without making use of `benchmark`
|
||||
library and with a fixed number of iterations.
|
||||
|
||||
```sh
|
||||
node benchmarks/<type>/<benchmark-name> --profile
|
||||
```
|
||||
@@ -1,82 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
var fs = require("fs");
|
||||
var async = require("async");
|
||||
var Benchmark = require("benchmark");
|
||||
var Connection = require("../lib/tedious").Connection;
|
||||
|
||||
function createConnection(cb) {
|
||||
var config = JSON.parse(fs.readFileSync(process.env.HOME + '/.tedious/test-connection.json', 'utf8')).config;
|
||||
|
||||
var connection = new Connection(config);
|
||||
connection.on("connect", function() {
|
||||
cb(connection);
|
||||
});
|
||||
}
|
||||
|
||||
function createBenchmark(test) {
|
||||
if (process.argv.indexOf("--profile") != -1) {
|
||||
process.nextTick(function() {
|
||||
runProfile(test);
|
||||
});
|
||||
} else {
|
||||
process.nextTick(function() {
|
||||
runBenchmark(test);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function runBenchmark(test) {
|
||||
var memStart, memMax = memStart = process.memoryUsage().rss;
|
||||
|
||||
test.setup(function(err) {
|
||||
if (err) throw err;
|
||||
|
||||
var bm = new Benchmark(test.name, {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
test.exec(function(err) {
|
||||
if (err) throw err;
|
||||
|
||||
memMax = Math.max(memMax, process.memoryUsage().rss);
|
||||
|
||||
deferred.resolve();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
bm.on("complete", function(event) {
|
||||
console.log(String(event.target))
|
||||
console.log("Memory:", (memMax - memStart)/1024/1024, "MiB")
|
||||
|
||||
test.teardown(function(err) {
|
||||
if (err) throw err;
|
||||
});
|
||||
});
|
||||
|
||||
bm.run({ "async": true });
|
||||
});
|
||||
}
|
||||
|
||||
function runProfile(test) {
|
||||
test.setup(function(err) {
|
||||
if (err) throw err;
|
||||
|
||||
async.timesSeries(test.profileIterations, function(n, done) {
|
||||
async.setImmediate(function() {
|
||||
console.log("[Iteration " + n + "]");
|
||||
test.exec(done);
|
||||
});
|
||||
}, function(err) {
|
||||
if (err) throw err;
|
||||
|
||||
test.teardown(function(err) {
|
||||
if (err) throw err;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports.createBenchmark = createBenchmark;
|
||||
module.exports.createConnection = createConnection;
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
var fs = require("fs");
|
||||
var path = require("path");
|
||||
var childProcess = require("child_process")
|
||||
var Benchmark = require("benchmark");
|
||||
|
||||
var Connection = require("../lib/tedious").Connection;
|
||||
var Request = require("../lib/tedious").Request;
|
||||
|
||||
var types = ["query", "token-parser"];
|
||||
var tests = [];
|
||||
|
||||
types.forEach(function(type) {
|
||||
var dir = path.join(__dirname, type);
|
||||
tests.push.apply(tests, fs.readdirSync(dir).map(function(file) {
|
||||
return path.join(dir, file);
|
||||
}));
|
||||
});
|
||||
|
||||
runBenchmarks();
|
||||
|
||||
function runBenchmarks() {
|
||||
var test = tests.shift();
|
||||
if (!test)
|
||||
return;
|
||||
|
||||
var child = childProcess.spawn(process.execPath, [ test ], { stdio: 'inherit' });
|
||||
child.on('close', function(code) {
|
||||
if (code) {
|
||||
process.exit(code);
|
||||
} else {
|
||||
runBenchmarks();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
var tedious = require("../../lib/tedious");
|
||||
var Request = tedious.Request;
|
||||
var TYPES = tedious.TYPES;
|
||||
var async = require("async");
|
||||
|
||||
var common = require("../common");
|
||||
|
||||
var connection;
|
||||
common.createBenchmark({
|
||||
name: "Many result rows",
|
||||
profileIterations: 10,
|
||||
|
||||
setup: function(cb) {
|
||||
common.createConnection(function(_connection) {
|
||||
connection = _connection;
|
||||
|
||||
var request = new Request("CREATE TABLE #benchmark ([id] int IDENTITY(1,1), [name] nvarchar(100), [description] nvarchar(max))", function(err) {
|
||||
if (err) return cb(err);
|
||||
|
||||
async.timesSeries(10000, function(n, next) {
|
||||
var request = new Request("INSERT INTO #benchmark ([name], [description]) VALUES (@name, @description)", next);
|
||||
|
||||
request.addParameter("name", TYPES.NVarChar, "Row " + n);
|
||||
request.addParameter("description", TYPES.NVarChar, "Example Test Description for Row " + n);
|
||||
|
||||
connection.execSql(request);
|
||||
}, cb);
|
||||
});
|
||||
|
||||
connection.execSqlBatch(request);
|
||||
});
|
||||
},
|
||||
|
||||
exec: function(cb) {
|
||||
var request = new Request("SELECT * FROM #benchmark", cb);
|
||||
connection.execSql(request);
|
||||
},
|
||||
|
||||
teardown: function(cb) {
|
||||
var request = new Request("DROP TABLE #benchmark", function(err) {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
connection.close();
|
||||
});
|
||||
connection.execSqlBatch(request);
|
||||
}
|
||||
});
|
||||
@@ -1,44 +0,0 @@
|
||||
var tedious = require("../../lib/tedious");
|
||||
var Request = tedious.Request;
|
||||
var TYPES = tedious.TYPES;
|
||||
|
||||
var common = require("../common");
|
||||
|
||||
var connection;
|
||||
common.createBenchmark({
|
||||
name: "inserting nvarchar(max) with 5242880 chars",
|
||||
|
||||
profileIterations: 100,
|
||||
|
||||
setup: function(cb) {
|
||||
common.createConnection(function(_connection) {
|
||||
connection = _connection;
|
||||
|
||||
var request = new Request("CREATE TABLE #benchmark ([value] nvarchar(max))", function(err) {
|
||||
if (err) return cb(err);
|
||||
|
||||
var request = new Request("INSERT INTO #benchmark ([value]) VALUES (@value)", cb);
|
||||
request.addParameter("value", TYPES.NVarChar, new Array(5 * 1024 * 1024).join("x"));
|
||||
connection.execSql(request);
|
||||
});
|
||||
|
||||
connection.execSqlBatch(request);
|
||||
});
|
||||
},
|
||||
|
||||
exec: function(cb) {
|
||||
var request = new Request("SELECT * FROM #benchmark", cb);
|
||||
connection.execSql(request);
|
||||
},
|
||||
|
||||
teardown: function(cb) {
|
||||
var request = new Request("DROP TABLE #benchmark", function(err) {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
connection.close();
|
||||
});
|
||||
connection.execSqlBatch(request);
|
||||
}
|
||||
});
|
||||
@@ -1,44 +0,0 @@
|
||||
var tedious = require("../../lib/tedious");
|
||||
var Request = tedious.Request;
|
||||
var TYPES = tedious.TYPES;
|
||||
|
||||
var common = require("../common");
|
||||
|
||||
var connection;
|
||||
common.createBenchmark({
|
||||
name: "inserting nvarchar(max) with 4 chars",
|
||||
|
||||
profileIterations: 10000,
|
||||
|
||||
setup: function(cb) {
|
||||
common.createConnection(function(_connection) {
|
||||
connection = _connection;
|
||||
|
||||
var request = new Request("CREATE TABLE #benchmark ([value] nvarchar(max))", function(err) {
|
||||
if (err) return cb(err);
|
||||
|
||||
var request = new Request("INSERT INTO #benchmark ([value]) VALUES (@value)", cb);
|
||||
request.addParameter("value", TYPES.NVarChar, "asdf");
|
||||
connection.execSql(request);
|
||||
});
|
||||
|
||||
connection.execSqlBatch(request);
|
||||
});
|
||||
},
|
||||
|
||||
exec: function(cb) {
|
||||
var request = new Request("SELECT * FROM #benchmark", cb);
|
||||
connection.execSql(request);
|
||||
},
|
||||
|
||||
teardown: function(cb) {
|
||||
var request = new Request("DROP TABLE #benchmark", function(err) {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
connection.close();
|
||||
});
|
||||
connection.execSqlBatch(request);
|
||||
}
|
||||
});
|
||||
@@ -1,42 +0,0 @@
|
||||
var tedious = require("../../lib/tedious");
|
||||
var Request = tedious.Request;
|
||||
var TYPES = tedious.TYPES;
|
||||
|
||||
var common = require("../common");
|
||||
|
||||
var connection;
|
||||
common.createBenchmark({
|
||||
name: "inserting varbinary(4) with 4 bytes",
|
||||
|
||||
setup: function(cb) {
|
||||
common.createConnection(function(_connection) {
|
||||
connection = _connection;
|
||||
|
||||
var request = new Request("CREATE TABLE #benchmark ([value] varbinary(4))", function(err) {
|
||||
if (err) return cb(err);
|
||||
|
||||
var request = new Request("INSERT INTO #benchmark ([value]) VALUES (@value)", cb);
|
||||
request.addParameter("value", TYPES.VarBinary, new Buffer("asdf"));
|
||||
connection.execSql(request);
|
||||
});
|
||||
|
||||
connection.execSqlBatch(request);
|
||||
});
|
||||
},
|
||||
|
||||
exec: function(cb) {
|
||||
var request = new Request("SELECT * FROM #benchmark", cb);
|
||||
connection.execSql(request);
|
||||
},
|
||||
|
||||
teardown: function(cb) {
|
||||
var request = new Request("DROP TABLE #benchmark", function(err) {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
connection.close();
|
||||
});
|
||||
connection.execSqlBatch(request);
|
||||
}
|
||||
});
|
||||
@@ -1,46 +0,0 @@
|
||||
var tedious = require("../../lib/tedious");
|
||||
var Request = tedious.Request;
|
||||
var TYPES = tedious.TYPES;
|
||||
|
||||
var common = require("../common");
|
||||
|
||||
var connection;
|
||||
common.createBenchmark({
|
||||
name: "inserting varbinary(max) with 50 MiB",
|
||||
|
||||
profileIterations: 20,
|
||||
|
||||
setup: function(cb) {
|
||||
common.createConnection(function(_connection) {
|
||||
connection = _connection;
|
||||
|
||||
var request = new Request("CREATE TABLE #benchmark ([value] varbinary(max))", function(err) {
|
||||
if (err) return cb(err);
|
||||
|
||||
var request = new Request("INSERT INTO #benchmark ([value]) VALUES (@value)", cb);
|
||||
var buf = new Buffer(50 * 1024 * 1024);
|
||||
buf.fill("x");
|
||||
request.addParameter("value", TYPES.VarBinary, buf);
|
||||
connection.execSql(request);
|
||||
});
|
||||
|
||||
connection.execSqlBatch(request);
|
||||
});
|
||||
},
|
||||
|
||||
exec: function(cb) {
|
||||
var request = new Request("SELECT * FROM #benchmark", cb);
|
||||
connection.execSql(request);
|
||||
},
|
||||
|
||||
teardown: function(cb) {
|
||||
var request = new Request("DROP TABLE #benchmark", function(err) {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
connection.close();
|
||||
});
|
||||
connection.execSqlBatch(request);
|
||||
}
|
||||
});
|
||||
@@ -1,46 +0,0 @@
|
||||
var tedious = require("../../lib/tedious");
|
||||
var Request = tedious.Request;
|
||||
var TYPES = tedious.TYPES;
|
||||
|
||||
var common = require("../common");
|
||||
|
||||
var connection;
|
||||
common.createBenchmark({
|
||||
name: "inserting varbinary(max) with 5 MiB",
|
||||
|
||||
profileIterations: 100,
|
||||
|
||||
setup: function(cb) {
|
||||
common.createConnection(function(_connection) {
|
||||
connection = _connection;
|
||||
|
||||
var request = new Request("CREATE TABLE #benchmark ([value] varbinary(max))", function(err) {
|
||||
if (err) return cb(err);
|
||||
|
||||
var request = new Request("INSERT INTO #benchmark ([value]) VALUES (@value)", cb);
|
||||
var buf = new Buffer(5 * 1024 * 1024);
|
||||
buf.fill("x");
|
||||
request.addParameter("value", TYPES.VarBinary, buf);
|
||||
connection.execSql(request);
|
||||
});
|
||||
|
||||
connection.execSqlBatch(request);
|
||||
});
|
||||
},
|
||||
|
||||
exec: function(cb) {
|
||||
var request = new Request("SELECT * FROM #benchmark", cb);
|
||||
connection.execSql(request);
|
||||
},
|
||||
|
||||
teardown: function(cb) {
|
||||
var request = new Request("DROP TABLE #benchmark", function(err) {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
connection.close();
|
||||
});
|
||||
connection.execSqlBatch(request);
|
||||
}
|
||||
});
|
||||
@@ -1,42 +0,0 @@
|
||||
var tedious = require("../../lib/tedious");
|
||||
var Request = tedious.Request;
|
||||
var TYPES = tedious.TYPES;
|
||||
|
||||
var common = require("../common");
|
||||
|
||||
var connection;
|
||||
common.createBenchmark({
|
||||
name: "inserting varbinary(max) with 4 bytes",
|
||||
|
||||
setup: function(cb) {
|
||||
common.createConnection(function(_connection) {
|
||||
connection = _connection;
|
||||
|
||||
var request = new Request("CREATE TABLE #benchmark ([value] varbinary(max))", function(err) {
|
||||
if (err) return cb(err);
|
||||
|
||||
var request = new Request("INSERT INTO #benchmark ([value]) VALUES (@value)", cb);
|
||||
request.addParameter("value", TYPES.VarBinary, new Buffer("asdf"));
|
||||
connection.execSql(request);
|
||||
});
|
||||
|
||||
connection.execSqlBatch(request);
|
||||
});
|
||||
},
|
||||
|
||||
exec: function(cb) {
|
||||
var request = new Request("SELECT * FROM #benchmark", cb);
|
||||
connection.execSql(request);
|
||||
},
|
||||
|
||||
teardown: function(cb) {
|
||||
var request = new Request("DROP TABLE #benchmark", function(err) {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
connection.close();
|
||||
});
|
||||
connection.execSqlBatch(request);
|
||||
}
|
||||
});
|
||||
@@ -1,42 +0,0 @@
|
||||
var tedious = require("../../lib/tedious");
|
||||
var Request = tedious.Request;
|
||||
var TYPES = tedious.TYPES;
|
||||
|
||||
var Parser = require("../../lib/token/token-stream-parser").Parser;
|
||||
|
||||
var common = require("../common");
|
||||
|
||||
var parser = new Parser({ token: function() { } }, {}, {});
|
||||
|
||||
var tokenCount = 50;
|
||||
var data = new Buffer(new Array(tokenCount).join("810300000000001000380269006400000000000900e7c8000904d00034046e0061006d006500000000000900e7ffff0904d000340b6400650073006300720069007000740069006f006e00"), "hex");
|
||||
|
||||
common.createBenchmark({
|
||||
name: "parsing `COLMETADATA` tokens",
|
||||
|
||||
profileIterations: 3000,
|
||||
|
||||
setup: function(cb) {
|
||||
cb();
|
||||
},
|
||||
|
||||
exec: function(cb) {
|
||||
var count = 0;
|
||||
|
||||
parser.on("columnMetadata", function() {
|
||||
count += 1;
|
||||
|
||||
if (count === tokenCount - 1) {
|
||||
parser.removeAllListeners("columnMetadata");
|
||||
|
||||
cb();
|
||||
}
|
||||
});
|
||||
|
||||
parser.addBuffer(data);
|
||||
},
|
||||
|
||||
teardown: function(cb) {
|
||||
cb();
|
||||
}
|
||||
});
|
||||
@@ -1,42 +0,0 @@
|
||||
var tedious = require("../../lib/tedious");
|
||||
var Request = tedious.Request;
|
||||
var TYPES = tedious.TYPES;
|
||||
|
||||
var Parser = require("../../lib/token/token-stream-parser").Parser;
|
||||
|
||||
var common = require("../common");
|
||||
|
||||
var parser = new Parser({ token: function() { } }, {}, {});
|
||||
|
||||
var tokenCount = 500;
|
||||
var data = new Buffer(new Array(tokenCount).join("FE0000E0000000000000000000"), "hex");
|
||||
|
||||
common.createBenchmark({
|
||||
name: "parsing `DONEPROC` tokens",
|
||||
|
||||
profileIterations: 3000,
|
||||
|
||||
setup: function(cb) {
|
||||
cb();
|
||||
},
|
||||
|
||||
exec: function(cb) {
|
||||
var count = 0;
|
||||
|
||||
parser.on("doneProc", function() {
|
||||
count += 1;
|
||||
|
||||
if (count === tokenCount - 1) {
|
||||
parser.removeAllListeners("doneProc");
|
||||
|
||||
cb();
|
||||
}
|
||||
});
|
||||
|
||||
parser.addBuffer(data);
|
||||
},
|
||||
|
||||
teardown: function(cb) {
|
||||
cb();
|
||||
}
|
||||
});
|
||||
@@ -1,366 +0,0 @@
|
||||
var tedious = require("../../lib/tedious");
|
||||
var Request = tedious.Request;
|
||||
var TYPES = tedious.TYPES;
|
||||
|
||||
var Parser = require("../../lib/token/token-stream-parser").Parser;
|
||||
|
||||
var common = require("../common");
|
||||
|
||||
var parser = new Parser({ token: function() { } }, {}, {});
|
||||
var data = new Buffer([
|
||||
"810300000000001000380269006400000000000900E7C8000904D00034046E00",
|
||||
"61006D006500000000000900E7FFFF0904D000340B6400650073006300720069",
|
||||
"007000740069006F006E00D1010000000A0052006F0077002000300044000000",
|
||||
"00000000440000004500780061006D0070006C00650020005400650073007400",
|
||||
"20004400650073006300720069007000740069006F006E00200066006F007200",
|
||||
"200052006F0077002000300000000000D1020000000A0052006F007700200031",
|
||||
"004400000000000000440000004500780061006D0070006C0065002000540065",
|
||||
"007300740020004400650073006300720069007000740069006F006E00200066",
|
||||
"006F007200200052006F0077002000310000000000D1030000000A0052006F00",
|
||||
"7700200032004400000000000000440000004500780061006D0070006C006500",
|
||||
"2000540065007300740020004400650073006300720069007000740069006F00",
|
||||
"6E00200066006F007200200052006F0077002000320000000000D1040000000A",
|
||||
"0052006F007700200033004400000000000000440000004500780061006D0070",
|
||||
"006C006500200054006500730074002000440065007300630072006900700074",
|
||||
"0069006F006E00200066006F007200200052006F0077002000330000000000D1",
|
||||
"050000000A0052006F0077002000340044000000000000004400000045007800",
|
||||
"61006D0070006C00650020005400650073007400200044006500730063007200",
|
||||
"69007000740069006F006E00200066006F007200200052006F00770020003400",
|
||||
"00000000D1060000000A0052006F007700200035004400000000000000440000",
|
||||
"004500780061006D0070006C0065002000540065007300740020004400650073",
|
||||
"006300720069007000740069006F006E00200066006F007200200052006F0077",
|
||||
"002000350000000000D1070000000A0052006F00770020003600440000000000",
|
||||
"0000440000004500780061006D0070006C006500200054006500730074002000",
|
||||
"4400650073006300720069007000740069006F006E00200066006F0072002000",
|
||||
"52006F0077002000360000000000D1080000000A0052006F0077002000370044",
|
||||
"00000000000000440000004500780061006D0070006C00650020005400650073",
|
||||
"00740020004400650073006300720069007000740069006F006E00200066006F",
|
||||
"007200200052006F0077002000370000000000D1090000000A0052006F007700",
|
||||
"200038004400000000000000440000004500780061006D0070006C0065002000",
|
||||
"540065007300740020004400650073006300720069007000740069006F006E00",
|
||||
"200066006F007200200052006F0077002000380000000000D10A0000000A0052",
|
||||
"006F007700200039004400000000000000440000004500780061006D0070006C",
|
||||
"0065002000540065007300740020004400650073006300720069007000740069",
|
||||
"006F006E00200066006F007200200052006F0077002000390000000000D10B00",
|
||||
"00000C0052006F00770020003100300046000000000000004600000045007800",
|
||||
"61006D0070006C00650020005400650073007400200044006500730063007200",
|
||||
"69007000740069006F006E00200066006F007200200052006F00770020003100",
|
||||
"300000000000D10C0000000C0052006F00770020003100310046000000000000",
|
||||
"00460000004500780061006D0070006C00650020005400650073007400200044",
|
||||
"00650073006300720069007000740069006F006E00200066006F007200200052",
|
||||
"006F00770020003100310000000000D10D0000000C0052006F00770020003100",
|
||||
"32004600000000000000460000004500780061006D0070006C00650020005400",
|
||||
"65007300740020004400650073006300720069007000740069006F006E002000",
|
||||
"66006F007200200052006F00770020003100320000000000D10E0000000C0052",
|
||||
"006F0077002000310033004600000000000000460000004500780061006D0070",
|
||||
"006C006500200054006500730074002000440065007300630072006900700074",
|
||||
"0069006F006E00200066006F007200200052006F007700200031003300000000",
|
||||
"00D10F0000000C0052006F007700200031003400460000000000000046000000",
|
||||
"4500780061006D0070006C006500200054006500730074002000440065007300",
|
||||
"6300720069007000740069006F006E00200066006F007200200052006F007700",
|
||||
"20003100340000000000D1100000000C0052006F007700200031003500460000",
|
||||
"0000000000460000004500780061006D0070006C006500200054006500730074",
|
||||
"0020004400650073006300720069007000740069006F006E00200066006F0072",
|
||||
"00200052006F00770020003100350000000000D1110000000C0052006F007700",
|
||||
"2000310036004600000000000000460000004500780061006D0070006C006500",
|
||||
"2000540065007300740020004400650073006300720069007000740069006F00",
|
||||
"6E00200066006F007200200052006F00770020003100360000000000D1120000",
|
||||
"000C0052006F0077002000310037004600000000000000460000004500780061",
|
||||
"006D0070006C0065002000540065007300740020004400650073006300720069",
|
||||
"007000740069006F006E00200066006F007200200052006F0077002000310037",
|
||||
"0000000000D1130000000C0052006F0077002000310038004600000000000000",
|
||||
"460000004500780061006D0070006C0065002000540065007300740020004400",
|
||||
"650073006300720069007000740069006F006E00200066006F00720020005200",
|
||||
"6F00770020003100380000000000D1140000000C0052006F0077002000310039",
|
||||
"004600000000000000460000004500780061006D0070006C0065002000540065",
|
||||
"007300740020004400650073006300720069007000740069006F006E00200066",
|
||||
"006F007200200052006F00770020003100390000000000D1150000000C005200",
|
||||
"6F0077002000320030004600000000000000460000004500780061006D007000",
|
||||
"6C00650020005400650073007400200044006500730063007200690070007400",
|
||||
"69006F006E00200066006F007200200052006F00770020003200300000000000",
|
||||
"D1160000000C0052006F00770020003200310046000000000000004600000045",
|
||||
"00780061006D0070006C00650020005400650073007400200044006500730063",
|
||||
"00720069007000740069006F006E00200066006F007200200052006F00770020",
|
||||
"003200310000000000D1170000000C0052006F00770020003200320046000000",
|
||||
"00000000460000004500780061006D0070006C00650020005400650073007400",
|
||||
"20004400650073006300720069007000740069006F006E00200066006F007200",
|
||||
"200052006F00770020003200320000000000D1180000000C0052006F00770020",
|
||||
"00320033004600000000000000460000004500780061006D0070006C00650020",
|
||||
"00540065007300740020004400650073006300720069007000740069006F006E",
|
||||
"00200066006F007200200052006F00770020003200330000000000D119000000",
|
||||
"0C0052006F007700200032003400460000000000000046000000450078006100",
|
||||
"6D0070006C006500200054006500730074002000440065007300630072006900",
|
||||
"7000740069006F006E00200066006F007200200052006F007700200032003400",
|
||||
"00000000D11A0000000C0052006F007700200032003500460000000000000046",
|
||||
"0000004500780061006D0070006C006500200054006500730074002000440065",
|
||||
"0073006300720069007000740069006F006E00200066006F007200200052006F",
|
||||
"00770020003200350000000000D11B0000000C0052006F007700200032003600",
|
||||
"4600000000000000460000004500780061006D0070006C006500200054006500",
|
||||
"7300740020004400650073006300720069007000740069006F006E0020006600",
|
||||
"6F007200200052006F00770020003200360000000000D11C0000000C0052006F",
|
||||
"0077002000320037004600000000000000460000004500780061006D0070006C",
|
||||
"0065002000540065007300740020004400650073006300720069007000740069",
|
||||
"006F006E00200066006F007200200052006F00770020003200370000000000D1",
|
||||
"1D0000000C0052006F0077002000320038004600000000000000460000004500",
|
||||
"780061006D0070006C0065002000540065007300740020004400650073006300",
|
||||
"720069007000740069006F006E00200066006F007200200052006F0077002000",
|
||||
"3200380000000000D11E0000000C0052006F0077002000320039004600000000",
|
||||
"000000460000004500780061006D0070006C0065002000540065007300740020",
|
||||
"004400650073006300720069007000740069006F006E00200066006F00720020",
|
||||
"0052006F00770020003200390000000000D11F0000000C0052006F0077002000",
|
||||
"330030004600000000000000460000004500780061006D0070006C0065002000",
|
||||
"540065007300740020004400650073006300720069007000740069006F006E00",
|
||||
"200066006F007200200052006F00770020003300300000000000D1200000000C",
|
||||
"0052006F0077002000330031004600000000000000460000004500780061006D",
|
||||
"0070006C00650020005400650073007400200044006500730063007200690070",
|
||||
"00740069006F006E00200066006F007200200052006F00770020003300310000",
|
||||
"000000D1210000000C0052006F00770020003300320046000000000000004600",
|
||||
"00004500780061006D0070006C00650020005400650073007400200044006500",
|
||||
"73006300720069007000740069006F006E00200066006F007200200052006F00",
|
||||
"770020003300320000000000D1220000000C0052006F00770020003300330046",
|
||||
"00000000000000460000004500780061006D0070006C00650020005400650073",
|
||||
"00740020004400650073006300720069007000740069006F006E00200066006F",
|
||||
"007200200052006F00770020003300330000000000D1230000000C0052006F00",
|
||||
"77002000330034004600000000000000460000004500780061006D0070006C00",
|
||||
"6500200054006500730074002000440065007300630072006900700074006900",
|
||||
"6F006E00200066006F007200200052006F00770020003300340000000000D124",
|
||||
"0000000C0052006F007700200033003500460000000000000046000000450078",
|
||||
"0061006D0070006C006500200054006500730074002000440065007300630072",
|
||||
"0069007000740069006F006E00200066006F007200200052006F007700200033",
|
||||
"00350000000000D1250000000C0052006F007700200033003600460000000000",
|
||||
"0000460000004500780061006D0070006C006500200054006500730074002000",
|
||||
"4400650073006300720069007000740069006F006E00200066006F0072002000",
|
||||
"52006F00770020003300360000000000D1260000000C0052006F007700200033",
|
||||
"0037004600000000000000460000004500780061006D0070006C006500200054",
|
||||
"0065007300740020004400650073006300720069007000740069006F006E0020",
|
||||
"0066006F007200200052006F00770020003300370000000000D1270000000C00",
|
||||
"52006F0077002000330038004600000000000000200000004500780061006D00",
|
||||
"70006C006500200054006500730074002000440065007300",
|
||||
"260000006300720069007000740069006F006E00200066006F00720020005200",
|
||||
"6F00770020003300380000000000D1280000000C0052006F0077002000330039",
|
||||
"004600000000000000460000004500780061006D0070006C0065002000540065",
|
||||
"007300740020004400650073006300720069007000740069006F006E00200066",
|
||||
"006F007200200052006F00770020003300390000000000D1290000000C005200",
|
||||
"6F0077002000340030004600000000000000460000004500780061006D007000",
|
||||
"6C00650020005400650073007400200044006500730063007200690070007400",
|
||||
"69006F006E00200066006F007200200052006F00770020003400300000000000",
|
||||
"D12A0000000C0052006F00770020003400310046000000000000004600000045",
|
||||
"00780061006D0070006C00650020005400650073007400200044006500730063",
|
||||
"00720069007000740069006F006E00200066006F007200200052006F00770020",
|
||||
"003400310000000000D12B0000000C0052006F00770020003400320046000000",
|
||||
"00000000460000004500780061006D0070006C00650020005400650073007400",
|
||||
"20004400650073006300720069007000740069006F006E00200066006F007200",
|
||||
"200052006F00770020003400320000000000D12C0000000C0052006F00770020",
|
||||
"00340033004600000000000000460000004500780061006D0070006C00650020",
|
||||
"00540065007300740020004400650073006300720069007000740069006F006E",
|
||||
"00200066006F007200200052006F00770020003400330000000000D12D000000",
|
||||
"0C0052006F007700200034003400460000000000000046000000450078006100",
|
||||
"6D0070006C006500200054006500730074002000440065007300630072006900",
|
||||
"7000740069006F006E00200066006F007200200052006F007700200034003400",
|
||||
"00000000D12E0000000C0052006F007700200034003500460000000000000046",
|
||||
"0000004500780061006D0070006C006500200054006500730074002000440065",
|
||||
"0073006300720069007000740069006F006E00200066006F007200200052006F",
|
||||
"00770020003400350000000000D12F0000000C0052006F007700200034003600",
|
||||
"4600000000000000460000004500780061006D0070006C006500200054006500",
|
||||
"7300740020004400650073006300720069007000740069006F006E0020006600",
|
||||
"6F007200200052006F00770020003400360000000000D1300000000C0052006F",
|
||||
"0077002000340037004600000000000000460000004500780061006D0070006C",
|
||||
"0065002000540065007300740020004400650073006300720069007000740069",
|
||||
"006F006E00200066006F007200200052006F00770020003400370000000000D1",
|
||||
"310000000C0052006F0077002000340038004600000000000000460000004500",
|
||||
"780061006D0070006C0065002000540065007300740020004400650073006300",
|
||||
"720069007000740069006F006E00200066006F007200200052006F0077002000",
|
||||
"3400380000000000D1320000000C0052006F0077002000340039004600000000",
|
||||
"000000460000004500780061006D0070006C0065002000540065007300740020",
|
||||
"004400650073006300720069007000740069006F006E00200066006F00720020",
|
||||
"0052006F00770020003400390000000000D1330000000C0052006F0077002000",
|
||||
"350030004600000000000000460000004500780061006D0070006C0065002000",
|
||||
"540065007300740020004400650073006300720069007000740069006F006E00",
|
||||
"200066006F007200200052006F00770020003500300000000000D1340000000C",
|
||||
"0052006F0077002000350031004600000000000000460000004500780061006D",
|
||||
"0070006C00650020005400650073007400200044006500730063007200690070",
|
||||
"00740069006F006E00200066006F007200200052006F00770020003500310000",
|
||||
"000000D1350000000C0052006F00770020003500320046000000000000004600",
|
||||
"00004500780061006D0070006C00650020005400650073007400200044006500",
|
||||
"73006300720069007000740069006F006E00200066006F007200200052006F00",
|
||||
"770020003500320000000000D1360000000C0052006F00770020003500330046",
|
||||
"00000000000000460000004500780061006D0070006C00650020005400650073",
|
||||
"00740020004400650073006300720069007000740069006F006E00200066006F",
|
||||
"007200200052006F00770020003500330000000000D1370000000C0052006F00",
|
||||
"77002000350034004600000000000000460000004500780061006D0070006C00",
|
||||
"6500200054006500730074002000440065007300630072006900700074006900",
|
||||
"6F006E00200066006F007200200052006F00770020003500340000000000D138",
|
||||
"0000000C0052006F007700200035003500460000000000000046000000450078",
|
||||
"0061006D0070006C006500200054006500730074002000440065007300630072",
|
||||
"0069007000740069006F006E00200066006F007200200052006F007700200035",
|
||||
"00350000000000D1390000000C0052006F007700200035003600460000000000",
|
||||
"0000460000004500780061006D0070006C006500200054006500730074002000",
|
||||
"4400650073006300720069007000740069006F006E00200066006F0072002000",
|
||||
"52006F00770020003500360000000000D13A0000000C0052006F007700200035",
|
||||
"0037004600000000000000460000004500780061006D0070006C006500200054",
|
||||
"0065007300740020004400650073006300720069007000740069006F006E0020",
|
||||
"0066006F007200200052006F00770020003500370000000000D13B0000000C00",
|
||||
"52006F0077002000350038004600000000000000460000004500780061006D00",
|
||||
"70006C0065002000540065007300740020004400650073006300720069007000",
|
||||
"740069006F006E00200066006F007200200052006F0077002000350038000000",
|
||||
"0000D13C0000000C0052006F0077002000350039004600000000000000460000",
|
||||
"004500780061006D0070006C0065002000540065007300740020004400650073",
|
||||
"006300720069007000740069006F006E00200066006F007200200052006F0077",
|
||||
"0020003500390000000000D13D0000000C0052006F0077002000360030004600",
|
||||
"000000000000460000004500780061006D0070006C0065002000540065007300",
|
||||
"740020004400650073006300720069007000740069006F006E00200066006F00",
|
||||
"7200200052006F00770020003600300000000000D13E0000000C0052006F0077",
|
||||
"002000360031004600000000000000460000004500780061006D0070006C0065",
|
||||
"002000540065007300740020004400650073006300720069007000740069006F",
|
||||
"006E00200066006F007200200052006F00770020003600310000000000D13F00",
|
||||
"00000C0052006F00770020003600320046000000000000004600000045007800",
|
||||
"61006D0070006C00650020005400650073007400200044006500730063007200",
|
||||
"69007000740069006F006E00200066006F007200200052006F00770020003600",
|
||||
"320000000000D1400000000C0052006F00770020003600330046000000000000",
|
||||
"00460000004500780061006D0070006C00650020005400650073007400200044",
|
||||
"00650073006300720069007000740069006F006E00200066006F007200200052",
|
||||
"006F00770020003600330000000000D1410000000C0052006F00770020003600",
|
||||
"34004600000000000000460000004500780061006D0070006C00650020005400",
|
||||
"65007300740020004400650073006300720069007000740069006F006E002000",
|
||||
"66006F007200200052006F00770020003600340000000000D1420000000C0052",
|
||||
"006F0077002000360035004600000000000000460000004500780061006D0070",
|
||||
"006C006500200054006500730074002000440065007300630072006900700074",
|
||||
"0069006F006E00200066006F007200200052006F007700200036003500000000",
|
||||
"00D1430000000C0052006F007700200036003600460000000000000046000000",
|
||||
"4500780061006D0070006C006500200054006500730074002000440065007300",
|
||||
"6300720069007000740069006F006E00200066006F007200200052006F007700",
|
||||
"20003600360000000000D1440000000C0052006F007700200036003700460000",
|
||||
"0000000000460000004500780061006D0070006C006500200054006500730074",
|
||||
"0020004400650073006300720069007000740069006F006E00200066006F0072",
|
||||
"00200052006F00770020003600370000000000D1450000000C0052006F007700",
|
||||
"2000360038004600000000000000460000004500780061006D0070006C006500",
|
||||
"2000540065007300740020004400650073006300720069007000740069006F00",
|
||||
"6E00200066006F007200200052006F00770020003600380000000000D1460000",
|
||||
"000C0052006F0077002000360039004600000000000000460000004500780061",
|
||||
"006D0070006C0065002000540065007300740020004400650073006300720069",
|
||||
"007000740069006F006E00200066006F007200200052006F0077002000360039",
|
||||
"0000000000D1470000000C0052006F0077002000370030004600000000000000",
|
||||
"460000004500780061006D0070006C0065002000540065007300740020004400",
|
||||
"650073006300720069007000740069006F006E00200066006F00720020005200",
|
||||
"6F00770020003700300000000000D1480000000C0052006F0077002000370031",
|
||||
"004600000000000000460000004500780061006D0070006C0065002000540065",
|
||||
"007300740020004400650073006300720069007000740069006F006E00200066",
|
||||
"006F007200200052006F00770020003700310000000000D1490000000C005200",
|
||||
"6F0077002000370032004600000000000000460000004500780061006D007000",
|
||||
"6C00650020005400650073007400200044006500730063007200690070007400",
|
||||
"69006F006E00200066006F007200200052006F00770020003700320000000000",
|
||||
"D14A0000000C0052006F00770020003700330046000000000000004600000045",
|
||||
"00780061006D0070006C00650020005400650073007400200044006500730063",
|
||||
"00720069007000740069006F006E00200066006F007200200052006F00770020",
|
||||
"003700330000000000D14B0000000C0052006F00770020003700340046000000",
|
||||
"00000000460000004500780061006D0070006C00650020005400650073007400",
|
||||
"20004400650073006300720069007000740069006F006E00200066006F007200",
|
||||
"200052006F00770020003700340000000000D14C0000000C0052006F00770020",
|
||||
"00370035004600000000000000460000004500780061006D0070006C00650020",
|
||||
"00540065007300740020004400650073006300720069007000740069006F006E",
|
||||
"00200066006F007200200052006F00770020003700350000000000D14D000000",
|
||||
"0C0052006F007700200037003600460000000000000046000000450078006100",
|
||||
"6D0070006C006500200054006500730074002000440065007300630072006900",
|
||||
"7000740069006F006E00200066006F007200200052006F007700200037003600",
|
||||
"00000000D14E0000000C0052006F007700200037003700460000000000000015",
|
||||
"0000004500780061006D0070006C00650020005400650073",
|
||||
"3100000000740020004400650073006300720069007000740069006F006E0020",
|
||||
"0066006F007200200052006F00770020003700370000000000D14F0000000C00",
|
||||
"52006F0077002000370038004600000000000000460000004500780061006D00",
|
||||
"70006C0065002000540065007300740020004400650073006300720069007000",
|
||||
"740069006F006E00200066006F007200200052006F0077002000370038000000",
|
||||
"0000D1500000000C0052006F0077002000370039004600000000000000460000",
|
||||
"004500780061006D0070006C0065002000540065007300740020004400650073",
|
||||
"006300720069007000740069006F006E00200066006F007200200052006F0077",
|
||||
"0020003700390000000000D1510000000C0052006F0077002000380030004600",
|
||||
"000000000000460000004500780061006D0070006C0065002000540065007300",
|
||||
"740020004400650073006300720069007000740069006F006E00200066006F00",
|
||||
"7200200052006F00770020003800300000000000D1520000000C0052006F0077",
|
||||
"002000380031004600000000000000460000004500780061006D0070006C0065",
|
||||
"002000540065007300740020004400650073006300720069007000740069006F",
|
||||
"006E00200066006F007200200052006F00770020003800310000000000D15300",
|
||||
"00000C0052006F00770020003800320046000000000000004600000045007800",
|
||||
"61006D0070006C00650020005400650073007400200044006500730063007200",
|
||||
"69007000740069006F006E00200066006F007200200052006F00770020003800",
|
||||
"320000000000D1540000000C0052006F00770020003800330046000000000000",
|
||||
"00460000004500780061006D0070006C00650020005400650073007400200044",
|
||||
"00650073006300720069007000740069006F006E00200066006F007200200052",
|
||||
"006F00770020003800330000000000D1550000000C0052006F00770020003800",
|
||||
"34004600000000000000460000004500780061006D0070006C00650020005400",
|
||||
"65007300740020004400650073006300720069007000740069006F006E002000",
|
||||
"66006F007200200052006F00770020003800340000000000D1560000000C0052",
|
||||
"006F0077002000380035004600000000000000460000004500780061006D0070",
|
||||
"006C006500200054006500730074002000440065007300630072006900700074",
|
||||
"0069006F006E00200066006F007200200052006F007700200038003500000000",
|
||||
"00D1570000000C0052006F007700200038003600460000000000000046000000",
|
||||
"4500780061006D0070006C006500200054006500730074002000440065007300",
|
||||
"6300720069007000740069006F006E00200066006F007200200052006F007700",
|
||||
"20003800360000000000D1580000000C0052006F007700200038003700460000",
|
||||
"0000000000460000004500780061006D0070006C006500200054006500730074",
|
||||
"0020004400650073006300720069007000740069006F006E00200066006F0072",
|
||||
"00200052006F00770020003800370000000000D1590000000C0052006F007700",
|
||||
"2000380038004600000000000000460000004500780061006D0070006C006500",
|
||||
"2000540065007300740020004400650073006300720069007000740069006F00",
|
||||
"6E00200066006F007200200052006F00770020003800380000000000D15A0000",
|
||||
"000C0052006F0077002000380039004600000000000000460000004500780061",
|
||||
"006D0070006C0065002000540065007300740020004400650073006300720069",
|
||||
"007000740069006F006E00200066006F007200200052006F0077002000380039",
|
||||
"0000000000D15B0000000C0052006F0077002000390030004600000000000000",
|
||||
"460000004500780061006D0070006C0065002000540065007300740020004400",
|
||||
"650073006300720069007000740069006F006E00200066006F00720020005200",
|
||||
"6F00770020003900300000000000D15C0000000C0052006F0077002000390031",
|
||||
"004600000000000000460000004500780061006D0070006C0065002000540065",
|
||||
"007300740020004400650073006300720069007000740069006F006E00200066",
|
||||
"006F007200200052006F00770020003900310000000000D15D0000000C005200",
|
||||
"6F0077002000390032004600000000000000460000004500780061006D007000",
|
||||
"6C00650020005400650073007400200044006500730063007200690070007400",
|
||||
"69006F006E00200066006F007200200052006F00770020003900320000000000",
|
||||
"D15E0000000C0052006F00770020003900330046000000000000004600000045",
|
||||
"00780061006D0070006C00650020005400650073007400200044006500730063",
|
||||
"00720069007000740069006F006E00200066006F007200200052006F00770020",
|
||||
"003900330000000000D15F0000000C0052006F00770020003900340046000000",
|
||||
"00000000460000004500780061006D0070006C00650020005400650073007400",
|
||||
"20004400650073006300720069007000740069006F006E00200066006F007200",
|
||||
"200052006F00770020003900340000000000D1600000000C0052006F00770020",
|
||||
"00390035004600000000000000460000004500780061006D0070006C00650020",
|
||||
"00540065007300740020004400650073006300720069007000740069006F006E",
|
||||
"00200066006F007200200052006F00770020003900350000000000D161000000",
|
||||
"0C0052006F007700200039003600460000000000000046000000450078006100",
|
||||
"6D0070006C006500200054006500730074002000440065007300630072006900",
|
||||
"7000740069006F006E00200066006F007200200052006F007700200039003600",
|
||||
"00000000D1620000000C0052006F007700200039003700460000000000000046",
|
||||
"0000004500780061006D0070006C006500200054006500730074002000440065",
|
||||
"0073006300720069007000740069006F006E00200066006F007200200052006F",
|
||||
"00770020003900370000000000D1630000000C0052006F007700200039003800",
|
||||
"4600000000000000460000004500780061006D0070006C006500200054006500",
|
||||
"7300740020004400650073006300720069007000740069006F006E0020006600",
|
||||
"6F007200200052006F00770020003900380000000000D1640000000C0052006F",
|
||||
"0077002000390039004600000000000000460000004500780061006D0070006C",
|
||||
"0065002000540065007300740020004400650073006300720069007000740069",
|
||||
"006F006E00200066006F007200200052006F00770020003900390000000000FF",
|
||||
"1100C10064000000000000007900000000FE0000E0000000000000000000"
|
||||
].join(""), "hex");
|
||||
|
||||
common.createBenchmark({
|
||||
name: "parsing tokens for 100 rows",
|
||||
|
||||
profileIterations: 1000,
|
||||
|
||||
setup: function(cb) {
|
||||
cb();
|
||||
},
|
||||
|
||||
exec: function(cb) {
|
||||
parser.on("doneProc", function() {
|
||||
parser.removeAllListeners("doneProc");
|
||||
|
||||
cb();
|
||||
});
|
||||
|
||||
parser.addBuffer(data);
|
||||
},
|
||||
|
||||
teardown: function(cb) {
|
||||
cb();
|
||||
}
|
||||
});
|
||||
@@ -1,24 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var TYPE = {
|
||||
QUERY_NOTIFICATIONS: 1,
|
||||
TXN_DESCRIPTOR: 2,
|
||||
TRACE_ACTIVITY: 3
|
||||
};
|
||||
|
||||
var TXNDESCRIPTOR_HEADER_DATA_LEN = 4 + 8;
|
||||
|
||||
var TXNDESCRIPTOR_HEADER_LEN = 4 + 2 + TXNDESCRIPTOR_HEADER_DATA_LEN;
|
||||
|
||||
module.exports.writeToTrackingBuffer = writeToTrackingBuffer;
|
||||
function writeToTrackingBuffer(buffer, txnDescriptor, outstandingRequestCount) {
|
||||
buffer.writeUInt32LE(0);
|
||||
buffer.writeUInt32LE(TXNDESCRIPTOR_HEADER_LEN);
|
||||
buffer.writeUInt16LE(TYPE.TXN_DESCRIPTOR);
|
||||
buffer.writeBuffer(txnDescriptor);
|
||||
buffer.writeUInt32LE(outstandingRequestCount);
|
||||
|
||||
var data = buffer.data;
|
||||
data.writeUInt32LE(data.length, 0);
|
||||
return buffer;
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
if (!Buffer.concat) {
|
||||
Buffer.concat = function (buffers) {
|
||||
var buffersCount = buffers.length;
|
||||
|
||||
var length = 0;
|
||||
for (var i = 0; i < buffersCount; i++) {
|
||||
var buffer = buffers[i];
|
||||
length += buffer.length;
|
||||
}
|
||||
|
||||
var result = new Buffer(length);
|
||||
var position = 0;
|
||||
for (var i = 0; i < buffersCount; i++) {
|
||||
var buffer = buffers[i];
|
||||
buffer.copy(result, position, 0);
|
||||
position += buffer.length;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
Buffer.prototype.toByteArray = function () {
|
||||
return Array.prototype.slice.call(this, 0);
|
||||
};
|
||||
|
||||
Buffer.prototype.equals = function (other) {
|
||||
if (this.length !== other.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0, len = this.length; i < len; i++) {
|
||||
if (this[i] !== other[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
@@ -1,236 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var _get = require('babel-runtime/helpers/get')['default'];
|
||||
|
||||
var _inherits = require('babel-runtime/helpers/inherits')['default'];
|
||||
|
||||
var _createClass = require('babel-runtime/helpers/create-class')['default'];
|
||||
|
||||
var _classCallCheck = require('babel-runtime/helpers/class-call-check')['default'];
|
||||
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var WritableTrackingBuffer = require('./tracking-buffer/tracking-buffer').WritableTrackingBuffer;
|
||||
var TOKEN_TYPE = require('./token/token').TYPE;
|
||||
|
||||
var FLAGS = {
|
||||
nullable: 1 << 0,
|
||||
caseSen: 1 << 1,
|
||||
updateableReadWrite: 1 << 2,
|
||||
updateableUnknown: 1 << 3,
|
||||
identity: 1 << 4,
|
||||
computed: 1 << 5, // introduced in TDS 7.2
|
||||
fixedLenCLRType: 1 << 8, // introduced in TDS 7.2
|
||||
sparseColumnSet: 1 << 10, // introduced in TDS 7.3.B
|
||||
hidden: 1 << 13, // introduced in TDS 7.2
|
||||
key: 1 << 14, // introduced in TDS 7.2
|
||||
nullableUnknown: 1 << 15 // introduced in TDS 7.2
|
||||
};
|
||||
|
||||
var DONE_STATUS = {
|
||||
FINAL: 0x00,
|
||||
MORE: 0x1,
|
||||
ERROR: 0x2,
|
||||
INXACT: 0x4,
|
||||
COUNT: 0x10,
|
||||
ATTN: 0x20,
|
||||
SRVERROR: 0x100
|
||||
};
|
||||
|
||||
module.exports = (function (_EventEmitter) {
|
||||
_inherits(BulkLoad, _EventEmitter);
|
||||
|
||||
function BulkLoad(table, options1, callback) {
|
||||
_classCallCheck(this, BulkLoad);
|
||||
|
||||
_get(Object.getPrototypeOf(BulkLoad.prototype), 'constructor', this).call(this);
|
||||
|
||||
this.error = undefined;
|
||||
this.canceled = false;
|
||||
|
||||
this.table = table;
|
||||
this.options = options1;
|
||||
this.callback = callback;
|
||||
this.columns = [];
|
||||
this.columnsByName = {};
|
||||
this.rowsData = new WritableTrackingBuffer(1024, 'ucs2', true);
|
||||
this.firstRowWritten = false;
|
||||
}
|
||||
|
||||
_createClass(BulkLoad, [{
|
||||
key: 'addColumn',
|
||||
value: function addColumn(name, type, options) {
|
||||
if (options == null) {
|
||||
options = {};
|
||||
}
|
||||
|
||||
if (this.firstRowWritten) {
|
||||
throw new Error('Columns cannot be added to bulk insert after the first row has been written.');
|
||||
}
|
||||
|
||||
var column = {
|
||||
type: type,
|
||||
name: name,
|
||||
value: null,
|
||||
output: options.output || (options.output = false),
|
||||
length: options.length,
|
||||
precision: options.precision,
|
||||
scale: options.scale,
|
||||
objName: options.objName || name,
|
||||
nullable: options.nullable
|
||||
};
|
||||
|
||||
if ((type.id & 0x30) === 0x20) {
|
||||
if (column.length == undefined && type.resolveLength) {
|
||||
column.length = type.resolveLength(column);
|
||||
}
|
||||
}
|
||||
|
||||
if (type.hasPrecision) {
|
||||
if (column.precision == undefined && type.resolvePrecision) {
|
||||
column.precision = type.resolvePrecision(column);
|
||||
}
|
||||
}
|
||||
|
||||
if (type.hasScale) {
|
||||
if (column.scale == undefined && type.resolveScale) {
|
||||
column.scale = type.resolveScale(column);
|
||||
}
|
||||
}
|
||||
|
||||
this.columns.push(column);
|
||||
|
||||
return this.columnsByName[name] = column;
|
||||
}
|
||||
}, {
|
||||
key: 'addRow',
|
||||
value: function addRow(row) {
|
||||
this.firstRowWritten = true;
|
||||
|
||||
if (arguments.length > 1 || !row || typeof row !== 'object') {
|
||||
// convert arguments to array in a way the optimizer can handle
|
||||
var arrTemp = new Array(arguments.length);
|
||||
for (var i = 0, len = arguments.length; i < len; i++) {
|
||||
var c = arguments[i];
|
||||
arrTemp[i] = c;
|
||||
}
|
||||
row = arrTemp;
|
||||
}
|
||||
|
||||
// write row token
|
||||
this.rowsData.writeUInt8(TOKEN_TYPE.ROW);
|
||||
|
||||
// write each column
|
||||
var arr = row instanceof Array;
|
||||
for (var i = 0, len = this.columns.length; i < len; i++) {
|
||||
var c = this.columns[i];
|
||||
c.type.writeParameterData(this.rowsData, {
|
||||
length: c.length,
|
||||
scale: c.scale,
|
||||
precision: c.precision,
|
||||
value: row[arr ? i : c.objName]
|
||||
}, this.options);
|
||||
}
|
||||
}
|
||||
}, {
|
||||
key: 'getBulkInsertSql',
|
||||
value: function getBulkInsertSql() {
|
||||
var sql = 'insert bulk ' + this.table + '(';
|
||||
for (var i = 0, len = this.columns.length; i < len; i++) {
|
||||
var c = this.columns[i];
|
||||
if (i !== 0) {
|
||||
sql += ', ';
|
||||
}
|
||||
sql += '[' + c.name + '] ' + c.type.declaration(c);
|
||||
}
|
||||
sql += ')';
|
||||
return sql;
|
||||
}
|
||||
}, {
|
||||
key: 'getTableCreationSql',
|
||||
value: function getTableCreationSql() {
|
||||
var sql = 'CREATE TABLE ' + this.table + '(\n';
|
||||
for (var i = 0, len = this.columns.length; i < len; i++) {
|
||||
var c = this.columns[i];
|
||||
if (i !== 0) {
|
||||
sql += ',\n';
|
||||
}
|
||||
sql += '[' + c.name + '] ' + c.type.declaration(c);
|
||||
if (c.nullable !== void 0) {
|
||||
sql += ' ' + (c.nullable ? 'NULL' : 'NOT NULL');
|
||||
}
|
||||
}
|
||||
sql += '\n)';
|
||||
return sql;
|
||||
}
|
||||
}, {
|
||||
key: 'getPayload',
|
||||
value: function getPayload() {
|
||||
// Create COLMETADATA token
|
||||
var metaData = this.getColMetaData();
|
||||
var length = metaData.length;
|
||||
|
||||
// row data
|
||||
var rows = this.rowsData.data;
|
||||
length += rows.length;
|
||||
|
||||
// Create DONE token
|
||||
// It might be nice to make DoneToken a class if anything needs to create them, but for now, just do it here
|
||||
var tBuf = new WritableTrackingBuffer(this.options.tdsVersion < '7_2' ? 9 : 13);
|
||||
tBuf.writeUInt8(TOKEN_TYPE.DONE);
|
||||
var status = DONE_STATUS.FINAL;
|
||||
tBuf.writeUInt16LE(status);
|
||||
tBuf.writeUInt16LE(0); // CurCmd (TDS ignores this)
|
||||
tBuf.writeUInt32LE(0); // row count - doesn't really matter
|
||||
if (this.options.tdsVersion >= '7_2') {
|
||||
tBuf.writeUInt32LE(0); // row count is 64 bits in >= TDS 7.2
|
||||
}
|
||||
|
||||
var done = tBuf.data;
|
||||
length += done.length;
|
||||
|
||||
// composite payload
|
||||
var payload = new WritableTrackingBuffer(length);
|
||||
payload.copyFrom(metaData);
|
||||
payload.copyFrom(rows);
|
||||
payload.copyFrom(done);
|
||||
return payload;
|
||||
}
|
||||
}, {
|
||||
key: 'getColMetaData',
|
||||
value: function getColMetaData() {
|
||||
var tBuf = new WritableTrackingBuffer(100, null, true);
|
||||
// TokenType
|
||||
tBuf.writeUInt8(TOKEN_TYPE.COLMETADATA);
|
||||
// Count
|
||||
tBuf.writeUInt16LE(this.columns.length);
|
||||
|
||||
for (var j = 0, len = this.columns.length; j < len; j++) {
|
||||
var c = this.columns[j];
|
||||
// UserType
|
||||
if (this.options.tdsVersion < '7_2') {
|
||||
tBuf.writeUInt16LE(0);
|
||||
} else {
|
||||
tBuf.writeUInt32LE(0);
|
||||
}
|
||||
|
||||
// Flags
|
||||
var flags = FLAGS.updateableReadWrite;
|
||||
if (c.nullable) {
|
||||
flags |= FLAGS.nullable;
|
||||
} else if (c.nullable === void 0 && this.options.tdsVersion >= '7_2') {
|
||||
flags |= FLAGS.nullableUnknown;
|
||||
}
|
||||
tBuf.writeUInt16LE(flags);
|
||||
|
||||
// TYPE_INFO
|
||||
c.type.writeTypeInfo(tBuf, c, this.options);
|
||||
|
||||
// ColName
|
||||
tBuf.writeBVarchar(c.name, 'ucs2');
|
||||
}
|
||||
return tBuf.data;
|
||||
}
|
||||
}]);
|
||||
|
||||
return BulkLoad;
|
||||
})(EventEmitter);
|
||||
@@ -1,186 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
// http://technet.microsoft.com/en-us/library/aa176553(v=sql.80).aspx
|
||||
module.exports.codepageByLcid = {
|
||||
0x436: 'CP1252',
|
||||
0x401: 'CP1256',
|
||||
0x801: 'CP1256',
|
||||
0xC01: 'CP1256',
|
||||
0x1001: 'CP1256',
|
||||
0x1401: 'CP1256',
|
||||
0x1801: 'CP1256',
|
||||
0x1C01: 'CP1256',
|
||||
0x2001: 'CP1256',
|
||||
0x2401: 'CP1256',
|
||||
0x2801: 'CP1256',
|
||||
0x2C01: 'CP1256',
|
||||
0x3001: 'CP1256',
|
||||
0x3401: 'CP1256',
|
||||
0x3801: 'CP1256',
|
||||
0x3C01: 'CP1256',
|
||||
0x4001: 'CP1256',
|
||||
0x42D: 'CP1252',
|
||||
0x423: 'CP1251',
|
||||
0x402: 'CP1251',
|
||||
0x403: 'CP1252',
|
||||
0x30404: 'CP950',
|
||||
0x404: 'CP950',
|
||||
0x804: 'CP936',
|
||||
0x20804: 'CP936',
|
||||
0x1004: 'CP936',
|
||||
0x41a: 'CP1250',
|
||||
0x405: 'CP1250',
|
||||
0x406: 'CP1252',
|
||||
0x413: 'CP1252',
|
||||
0x813: 'CP1252',
|
||||
0x409: 'CP1252',
|
||||
0x809: 'CP1252',
|
||||
0x1009: 'CP1252',
|
||||
0x1409: 'CP1252',
|
||||
0xC09: 'CP1252',
|
||||
0x1809: 'CP1252',
|
||||
0x1C09: 'CP1252',
|
||||
0x2409: 'CP1252',
|
||||
0x2009: 'CP1252',
|
||||
0x425: 'CP1257',
|
||||
0x0438: 'CP1252',
|
||||
0x429: 'CP1256',
|
||||
0x40B: 'CP1252',
|
||||
0x40C: 'CP1252',
|
||||
0x80C: 'CP1252',
|
||||
0x100C: 'CP1252',
|
||||
0xC0C: 'CP1252',
|
||||
0x140C: 'CP1252',
|
||||
0x10437: 'CP1252',
|
||||
0x10407: 'CP1252',
|
||||
0x407: 'CP1252',
|
||||
0x807: 'CP1252',
|
||||
0xC07: 'CP1252',
|
||||
0x1007: 'CP1252',
|
||||
0x1407: 'CP1252',
|
||||
0x408: 'CP1253',
|
||||
0x40D: 'CP1255',
|
||||
0x439: 'CPUTF8',
|
||||
0x40E: 'CP1250',
|
||||
0x104E: 'CP1250',
|
||||
0x40F: 'CP1252',
|
||||
0x421: 'CP1252',
|
||||
0x410: 'CP1252',
|
||||
0x810: 'CP1252',
|
||||
0x411: 'CP932',
|
||||
0x10411: 'CP932',
|
||||
0x412: 'CP949',
|
||||
0x426: 'CP1257',
|
||||
0x427: 'CP1257',
|
||||
0x827: 'CP1257',
|
||||
0x41C: 'CP1251',
|
||||
0x414: 'CP1252',
|
||||
0x814: 'CP1252',
|
||||
0x415: 'CP1250',
|
||||
0x816: 'CP1252',
|
||||
0x416: 'CP1252',
|
||||
0x418: 'CP1250',
|
||||
0x419: 'CP1251',
|
||||
0x81A: 'CP1251',
|
||||
0xC1A: 'CP1251',
|
||||
0x41B: 'CP1250',
|
||||
0x424: 'CP1250',
|
||||
0x80A: 'CP1252',
|
||||
0x40A: 'CP1252',
|
||||
0xC0A: 'CP1252',
|
||||
0x100A: 'CP1252',
|
||||
0x140A: 'CP1252',
|
||||
0x180A: 'CP1252',
|
||||
0x1C0A: 'CP1252',
|
||||
0x200A: 'CP1252',
|
||||
0x240A: 'CP1252',
|
||||
0x280A: 'CP1252',
|
||||
0x2C0A: 'CP1252',
|
||||
0x300A: 'CP1252',
|
||||
0x340A: 'CP1252',
|
||||
0x380A: 'CP1252',
|
||||
0x3C0A: 'CP1252',
|
||||
0x400A: 'CP1252',
|
||||
0x41D: 'CP1252',
|
||||
0x41E: 'CP874',
|
||||
0x41F: 'CP1254',
|
||||
0x422: 'CP1251',
|
||||
0x420: 'CP1256',
|
||||
0x42A: 'CP1258'
|
||||
};
|
||||
|
||||
module.exports.codepageBySortId = {
|
||||
30: 'CP437', // SQL_Latin1_General_CP437_BIN
|
||||
31: 'CP437', // SQL_Latin1_General_CP437_CS_AS
|
||||
32: 'CP437', // SQL_Latin1_General_CP437_CI_AS
|
||||
33: 'CP437', // SQL_Latin1_General_Pref_CP437_CI_AS
|
||||
34: 'CP437', // SQL_Latin1_General_CP437_CI_AI
|
||||
40: 'CP850', // SQL_Latin1_General_CP850_BIN
|
||||
41: 'CP850', // SQL_Latin1_General_CP850_CS_AS
|
||||
42: 'CP850', // SQL_Latin1_General_CP850_CI_AS
|
||||
43: 'CP850', // SQL_Latin1_General_Pref_CP850_CI_AS
|
||||
44: 'CP850', // SQL_Latin1_General_CP850_CI_AI
|
||||
49: 'CP850', // SQL_1xCompat_CP850_CI_AS
|
||||
51: 'CP1252', // SQL_Latin1_General_Cp1_CS_AS_KI_WI
|
||||
52: 'CP1252', // SQL_Latin1_General_Cp1_CI_AS_KI_WI
|
||||
53: 'CP1252', // SQL_Latin1_General_Pref_Cp1_CI_AS_KI_WI
|
||||
54: 'CP1252', // SQL_Latin1_General_Cp1_CI_AI_KI_WI
|
||||
55: 'CP850', // SQL_AltDiction_CP850_CS_AS
|
||||
56: 'CP850', // SQL_AltDiction_Pref_CP850_CI_AS
|
||||
57: 'CP850', // SQL_AltDiction_CP850_CI_AI
|
||||
58: 'CP850', // SQL_Scandinavian_Pref_CP850_CI_AS
|
||||
59: 'CP850', // SQL_Scandinavian_CP850_CS_AS
|
||||
60: 'CP850', // SQL_Scandinavian_CP850_CI_AS
|
||||
61: 'CP850', // SQL_AltDiction_CP850_CI_AS
|
||||
80: 'CP1250', // SQL_Latin1_General_1250_BIN
|
||||
81: 'CP1250', // SQL_Latin1_General_CP1250_CS_AS
|
||||
82: 'CP1250', // SQL_Latin1_General_Cp1250_CI_AS_KI_WI
|
||||
83: 'CP1250', // SQL_Czech_Cp1250_CS_AS_KI_WI
|
||||
84: 'CP1250', // SQL_Czech_Cp1250_CI_AS_KI_WI
|
||||
85: 'CP1250', // SQL_Hungarian_Cp1250_CS_AS_KI_WI
|
||||
86: 'CP1250', // SQL_Hungarian_Cp1250_CI_AS_KI_WI
|
||||
87: 'CP1250', // SQL_Polish_Cp1250_CS_AS_KI_WI
|
||||
88: 'CP1250', // SQL_Polish_Cp1250_CI_AS_KI_WI
|
||||
89: 'CP1250', // SQL_Romanian_Cp1250_CS_AS_KI_WI
|
||||
90: 'CP1250', // SQL_Romanian_Cp1250_CI_AS_KI_WI
|
||||
91: 'CP1250', // SQL_Croatian_Cp1250_CS_AS_KI_WI
|
||||
92: 'CP1250', // SQL_Croatian_Cp1250_CI_AS_KI_WI
|
||||
93: 'CP1250', // SQL_Slovak_Cp1250_CS_AS_KI_WI
|
||||
94: 'CP1250', // SQL_Slovak_Cp1250_CI_AS_KI_WI
|
||||
95: 'CP1250', // SQL_Slovenian_Cp1250_CS_AS_KI_WI
|
||||
96: 'CP1250', // SQL_Slovenian_Cp1250_CI_AS_KI_WI
|
||||
104: 'CP1251', // SQL_Latin1_General_1251_BIN
|
||||
105: 'CP1251', // SQL_Latin1_General_CP1251_CS_AS
|
||||
106: 'CP1251', // SQL_Latin1_General_CP1251_CI_AS
|
||||
107: 'CP1251', // SQL_Ukrainian_Cp1251_CS_AS_KI_WI
|
||||
108: 'CP1251', // SQL_Ukrainian_Cp1251_CI_AS_KI_WI
|
||||
112: 'CP1253', // SQL_Latin1_General_1253_BIN
|
||||
113: 'CP1253', // SQL_Latin1_General_CP1253_CS_AS
|
||||
114: 'CP1253', // SQL_Latin1_General_CP1253_CI_AS
|
||||
120: 'CP1253', // SQL_MixDiction_CP1253_CS_AS
|
||||
121: 'CP1253', // SQL_AltDiction_CP1253_CS_AS
|
||||
122: 'CP1253', // SQL_AltDiction2_CP1253_CS_AS
|
||||
124: 'CP1253', // SQL_Latin1_General_CP1253_CI_AI
|
||||
128: 'CP1254', // SQL_Latin1_General_1254_BIN
|
||||
129: 'CP1254', // SQL_Latin1_General_Cp1254_CS_AS_KI_WI
|
||||
130: 'CP1254', // SQL_Latin1_General_Cp1254_CI_AS_KI_WI
|
||||
136: 'CP1255', // SQL_Latin1_General_1255_BIN
|
||||
137: 'CP1255', // SQL_Latin1_General_CP1255_CS_AS
|
||||
138: 'CP1255', // SQL_Latin1_General_CP1255_CI_AS
|
||||
144: 'CP1256', // SQL_Latin1_General_1256_BIN
|
||||
145: 'CP1256', // SQL_Latin1_General_CP1256_CS_AS
|
||||
146: 'CP1256', // SQL_Latin1_General_CP1256_CI_AS
|
||||
152: 'CP1257', // SQL_Latin1_General_1257_BIN
|
||||
153: 'CP1257', // SQL_Latin1_General_CP1257_CS_AS
|
||||
154: 'CP1257', // SQL_Latin1_General_CP1257_CI_AS
|
||||
155: 'CP1257', // SQL_Estonian_Cp1257_CS_AS_KI_WI
|
||||
156: 'CP1257', // SQL_Estonian_Cp1257_CI_AS_KI_WI
|
||||
157: 'CP1257', // SQL_Latvian_Cp1257_CS_AS_KI_WI
|
||||
158: 'CP1257', // SQL_Latvian_Cp1257_CI_AS_KI_WI
|
||||
159: 'CP1257', // SQL_Lithuanian_Cp1257_CS_AS_KI_WI
|
||||
160: 'CP1257', // SQL_Lithuanian_Cp1257_CI_AS_KI_WI
|
||||
183: 'CP1252', // SQL_Danish_Pref_Cp1_CI_AS_KI_WI
|
||||
184: 'CP1252', // SQL_SwedishPhone_Pref_Cp1_CI_AS_KI_WI
|
||||
185: 'CP1252', // SQL_SwedishStd_Pref_Cp1_CI_AS_KI_WI
|
||||
186: 'CP1252' // SQL_Icelandic_Pref_Cp1_CI_AS_KI_WI
|
||||
};
|
||||
@@ -1,80 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var _get = require('babel-runtime/helpers/get')['default'];
|
||||
|
||||
var _inherits = require('babel-runtime/helpers/inherits')['default'];
|
||||
|
||||
var _createClass = require('babel-runtime/helpers/create-class')['default'];
|
||||
|
||||
var _classCallCheck = require('babel-runtime/helpers/class-call-check')['default'];
|
||||
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var util = require('util');
|
||||
|
||||
module.exports = (function (_EventEmitter) {
|
||||
_inherits(Debug, _EventEmitter);
|
||||
|
||||
/*
|
||||
@options Which debug details should be sent.
|
||||
data - dump of packet data
|
||||
payload - details of decoded payload
|
||||
*/
|
||||
|
||||
function Debug(options) {
|
||||
_classCallCheck(this, Debug);
|
||||
|
||||
_get(Object.getPrototypeOf(Debug.prototype), 'constructor', this).call(this);
|
||||
|
||||
this.options = options;
|
||||
this.options = this.options || {};
|
||||
this.options.data = this.options.data || false;
|
||||
this.options.payload = this.options.payload || false;
|
||||
this.options.packet = this.options.packet || false;
|
||||
this.options.token = this.options.token || false;
|
||||
this.indent = ' ';
|
||||
}
|
||||
|
||||
_createClass(Debug, [{
|
||||
key: 'packet',
|
||||
value: function packet(direction, _packet) {
|
||||
if (this.haveListeners() && this.options.packet) {
|
||||
this.log('');
|
||||
this.log(direction);
|
||||
this.log(_packet.headerToString(this.indent));
|
||||
}
|
||||
}
|
||||
}, {
|
||||
key: 'data',
|
||||
value: function data(packet) {
|
||||
if (this.haveListeners() && this.options.data) {
|
||||
this.log(packet.dataToString(this.indent));
|
||||
}
|
||||
}
|
||||
}, {
|
||||
key: 'payload',
|
||||
value: function payload(generatePayloadText) {
|
||||
if (this.haveListeners() && this.options.payload) {
|
||||
this.log(generatePayloadText());
|
||||
}
|
||||
}
|
||||
}, {
|
||||
key: 'token',
|
||||
value: function token(_token) {
|
||||
if (this.haveListeners() && this.options.token) {
|
||||
this.log(util.inspect(_token, false, 5, true));
|
||||
}
|
||||
}
|
||||
}, {
|
||||
key: 'haveListeners',
|
||||
value: function haveListeners() {
|
||||
return this.listeners('debug').length > 0;
|
||||
}
|
||||
}, {
|
||||
key: 'log',
|
||||
value: function log(text) {
|
||||
this.emit('debug', text);
|
||||
}
|
||||
}]);
|
||||
|
||||
return Debug;
|
||||
})(EventEmitter);
|
||||
@@ -1,47 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var util = require('util');
|
||||
|
||||
module.exports.ConnectionError = ConnectionError;
|
||||
function ConnectionError(message, code) {
|
||||
if (!(this instanceof ConnectionError)) {
|
||||
if (message instanceof ConnectionError) {
|
||||
return message;
|
||||
}
|
||||
|
||||
return new ConnectionError(message, code);
|
||||
}
|
||||
|
||||
Error.call(this);
|
||||
|
||||
this.message = message;
|
||||
this.code = code;
|
||||
|
||||
Error.captureStackTrace(this, this.constructor);
|
||||
}
|
||||
|
||||
util.inherits(ConnectionError, Error);
|
||||
|
||||
ConnectionError.prototype.name = 'ConnectionError';
|
||||
|
||||
module.exports.RequestError = RequestError;
|
||||
function RequestError(message, code) {
|
||||
if (!(this instanceof RequestError)) {
|
||||
if (message instanceof RequestError) {
|
||||
return message;
|
||||
}
|
||||
|
||||
return new RequestError(message, code);
|
||||
}
|
||||
|
||||
Error.call(this);
|
||||
|
||||
this.message = message;
|
||||
this.code = code;
|
||||
|
||||
Error.captureStackTrace(this, this.constructor);
|
||||
}
|
||||
|
||||
util.inherits(RequestError, Error);
|
||||
|
||||
RequestError.prototype.name = 'RequestError';
|
||||
@@ -1,19 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
function formatHex(number) {
|
||||
var hex = number.toString(16);
|
||||
if (hex.length === 1) {
|
||||
hex = '0' + hex;
|
||||
}
|
||||
return hex;
|
||||
}
|
||||
|
||||
module.exports.arrayToGuid = arrayToGuid;
|
||||
function arrayToGuid(array) {
|
||||
return (formatHex(array[3]) + formatHex(array[2]) + formatHex(array[1]) + formatHex(array[0]) + '-' + formatHex(array[5]) + formatHex(array[4]) + '-' + formatHex(array[7]) + formatHex(array[6]) + '-' + formatHex(array[8]) + formatHex(array[9]) + '-' + formatHex(array[10]) + formatHex(array[11]) + formatHex(array[12]) + formatHex(array[13]) + formatHex(array[14]) + formatHex(array[15])).toUpperCase();
|
||||
}
|
||||
|
||||
module.exports.guidToArray = guidToArray;
|
||||
function guidToArray(guid) {
|
||||
return [parseInt(guid.substring(6, 8), 16), parseInt(guid.substring(4, 6), 16), parseInt(guid.substring(2, 4), 16), parseInt(guid.substring(0, 2), 16), parseInt(guid.substring(11, 13), 16), parseInt(guid.substring(9, 11), 16), parseInt(guid.substring(16, 18), 16), parseInt(guid.substring(14, 16), 16), parseInt(guid.substring(19, 21), 16), parseInt(guid.substring(21, 23), 16), parseInt(guid.substring(24, 26), 16), parseInt(guid.substring(26, 28), 16), parseInt(guid.substring(28, 30), 16), parseInt(guid.substring(30, 32), 16), parseInt(guid.substring(32, 34), 16), parseInt(guid.substring(34, 36), 16)];
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var dgram = require('dgram');
|
||||
|
||||
var SQL_SERVER_BROWSER_PORT = 1434;
|
||||
var TIMEOUT = 2 * 1000;
|
||||
var RETRIES = 3;
|
||||
// There are three bytes at the start of the response, whose purpose is unknown.
|
||||
var MYSTERY_HEADER_LENGTH = 3;
|
||||
|
||||
// Most of the functionality has been determined from from jTDS's MSSqlServerInfo class.
|
||||
module.exports.instanceLookup = instanceLookup;
|
||||
function instanceLookup(server, instanceName, callback, timeout, retries) {
|
||||
var socket = undefined,
|
||||
timer = undefined;
|
||||
timeout = timeout || TIMEOUT;
|
||||
var retriesLeft = retries || RETRIES;
|
||||
|
||||
function onMessage(message) {
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
timer = undefined;
|
||||
}
|
||||
message = message.toString('ascii', MYSTERY_HEADER_LENGTH);
|
||||
var port = parseBrowserResponse(message, instanceName);
|
||||
socket.close();
|
||||
if (port) {
|
||||
return callback(undefined, port);
|
||||
} else {
|
||||
return callback('Port for ' + instanceName + ' not found in ' + message);
|
||||
}
|
||||
}
|
||||
|
||||
function onError(err) {
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
timer = undefined;
|
||||
}
|
||||
socket.close();
|
||||
return callback('Failed to lookup instance on ' + server + ' - ' + err.message);
|
||||
}
|
||||
|
||||
function onTimeout() {
|
||||
timer = undefined;
|
||||
socket.close();
|
||||
return makeAttempt();
|
||||
}
|
||||
|
||||
function makeAttempt() {
|
||||
if (retriesLeft > 0) {
|
||||
retriesLeft--;
|
||||
var request = new Buffer([0x02]);
|
||||
socket = dgram.createSocket('udp4');
|
||||
socket.on('error', onError);
|
||||
socket.on('message', onMessage);
|
||||
socket.send(request, 0, request.length, SQL_SERVER_BROWSER_PORT, server);
|
||||
return timer = setTimeout(onTimeout, timeout);
|
||||
} else {
|
||||
return callback('Failed to get response from SQL Server Browser on ' + server);
|
||||
}
|
||||
}
|
||||
|
||||
return makeAttempt();
|
||||
}
|
||||
|
||||
module.exports.parseBrowserResponse = parseBrowserResponse;
|
||||
function parseBrowserResponse(response, instanceName) {
|
||||
var getPort = undefined;
|
||||
|
||||
var instances = response.split(';;');
|
||||
for (var i = 0, len = instances.length; i < len; i++) {
|
||||
var instance = instances[i];
|
||||
var parts = instance.split(';');
|
||||
|
||||
for (var p = 0, partsLen = parts.length; p < partsLen; p += 2) {
|
||||
var _name = parts[p];
|
||||
var value = parts[p + 1];
|
||||
|
||||
if (_name === 'tcp' && getPort) {
|
||||
var port = parseInt(value, 10);
|
||||
return port;
|
||||
}
|
||||
|
||||
if (_name === 'InstanceName') {
|
||||
if (value.toUpperCase() === instanceName.toUpperCase()) {
|
||||
getPort = true;
|
||||
} else {
|
||||
getPort = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
module.exports.name = 'Tedious';
|
||||
@@ -1,295 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var _createClass = require('babel-runtime/helpers/create-class')['default'];
|
||||
|
||||
var _classCallCheck = require('babel-runtime/helpers/class-call-check')['default'];
|
||||
|
||||
require('./buffertools');
|
||||
|
||||
var WritableTrackingBuffer = require('./tracking-buffer/writable-tracking-buffer');
|
||||
var os = require('os');
|
||||
var sprintf = require('sprintf').sprintf;
|
||||
var libraryName = require('./library').name;
|
||||
var versions = require('./tds-versions').versions;
|
||||
|
||||
var FLAGS_1 = {
|
||||
ENDIAN_LITTLE: 0x00,
|
||||
ENDIAN_BIG: 0x01,
|
||||
CHARSET_ASCII: 0x00,
|
||||
CHARSET_EBCDIC: 0x02,
|
||||
FLOAT_IEEE_754: 0x00,
|
||||
FLOAT_VAX: 0x04,
|
||||
FLOAT_ND5000: 0x08,
|
||||
BCP_DUMPLOAD_ON: 0x00,
|
||||
BCP_DUMPLOAD_OFF: 0x10,
|
||||
USE_DB_ON: 0x00,
|
||||
USE_DB_OFF: 0x20,
|
||||
INIT_DB_WARN: 0x00,
|
||||
INIT_DB_FATAL: 0x40,
|
||||
SET_LANG_WARN_OFF: 0x00,
|
||||
SET_LANG_WARN_ON: 0x80
|
||||
};
|
||||
|
||||
var FLAGS_2 = {
|
||||
INIT_LANG_WARN: 0x00,
|
||||
INIT_LANG_FATAL: 0x01,
|
||||
ODBC_OFF: 0x00,
|
||||
ODBC_ON: 0x02,
|
||||
F_TRAN_BOUNDARY: 0x04,
|
||||
F_CACHE_CONNECT: 0x08,
|
||||
USER_NORMAL: 0x00,
|
||||
USER_SERVER: 0x10,
|
||||
USER_REMUSER: 0x20,
|
||||
USER_SQLREPL: 0x40,
|
||||
INTEGRATED_SECURITY_OFF: 0x00,
|
||||
INTEGRATED_SECURITY_ON: 0x80
|
||||
};
|
||||
|
||||
var TYPE_FLAGS = {
|
||||
SQL_DFLT: 0x00,
|
||||
SQL_TSQL: 0x08,
|
||||
OLEDB_OFF: 0x00,
|
||||
OLEDB_ON: 0x10,
|
||||
READ_WRITE_INTENT: 0x00,
|
||||
READ_ONLY_INTENT: 0x20
|
||||
};
|
||||
|
||||
var FLAGS_3 = {
|
||||
CHANGE_PASSWORD_NO: 0x00,
|
||||
CHANGE_PASSWORD_YES: 0x01,
|
||||
BINARY_XML: 0x02,
|
||||
SPAWN_USER_INSTANCE: 0x04,
|
||||
UNKNOWN_COLLATION_HANDLING: 0x08
|
||||
};
|
||||
|
||||
var NTLMFlags = {
|
||||
NTLM_NegotiateUnicode: 0x00000001,
|
||||
NTLM_NegotiateOEM: 0x00000002,
|
||||
NTLM_RequestTarget: 0x00000004,
|
||||
NTLM_Unknown9: 0x00000008,
|
||||
NTLM_NegotiateSign: 0x00000010,
|
||||
NTLM_NegotiateSeal: 0x00000020,
|
||||
NTLM_NegotiateDatagram: 0x00000040,
|
||||
NTLM_NegotiateLanManagerKey: 0x00000080,
|
||||
NTLM_Unknown8: 0x00000100,
|
||||
NTLM_NegotiateNTLM: 0x00000200,
|
||||
NTLM_NegotiateNTOnly: 0x00000400,
|
||||
NTLM_Anonymous: 0x00000800,
|
||||
NTLM_NegotiateOemDomainSupplied: 0x00001000,
|
||||
NTLM_NegotiateOemWorkstationSupplied: 0x00002000,
|
||||
NTLM_Unknown6: 0x00004000,
|
||||
NTLM_NegotiateAlwaysSign: 0x00008000,
|
||||
NTLM_TargetTypeDomain: 0x00010000,
|
||||
NTLM_TargetTypeServer: 0x00020000,
|
||||
NTLM_TargetTypeShare: 0x00040000,
|
||||
NTLM_NegotiateExtendedSecurity: 0x00080000,
|
||||
NTLM_NegotiateIdentify: 0x00100000,
|
||||
NTLM_Unknown5: 0x00200000,
|
||||
NTLM_RequestNonNTSessionKey: 0x00400000,
|
||||
NTLM_NegotiateTargetInfo: 0x00800000,
|
||||
NTLM_Unknown4: 0x01000000,
|
||||
NTLM_NegotiateVersion: 0x02000000,
|
||||
NTLM_Unknown3: 0x04000000,
|
||||
NTLM_Unknown2: 0x08000000,
|
||||
NTLM_Unknown1: 0x10000000,
|
||||
NTLM_Negotiate128: 0x20000000,
|
||||
NTLM_NegotiateKeyExchange: 0x40000000,
|
||||
NTLM_Negotiate56: 0x80000000
|
||||
};
|
||||
|
||||
/*
|
||||
s2.2.6.3
|
||||
*/
|
||||
module.exports = (function () {
|
||||
function Login7Payload(loginData) {
|
||||
_classCallCheck(this, Login7Payload);
|
||||
|
||||
this.loginData = loginData;
|
||||
|
||||
var lengthLength = 4;
|
||||
var fixed = this.createFixedData();
|
||||
var variable = this.createVariableData(lengthLength + fixed.length);
|
||||
var length = lengthLength + fixed.length + variable.length;
|
||||
var data = new WritableTrackingBuffer(300);
|
||||
data.writeUInt32LE(length);
|
||||
data.writeBuffer(fixed);
|
||||
data.writeBuffer(variable);
|
||||
this.data = data.data;
|
||||
}
|
||||
|
||||
_createClass(Login7Payload, [{
|
||||
key: 'createFixedData',
|
||||
value: function createFixedData() {
|
||||
this.tdsVersion = versions[this.loginData.tdsVersion];
|
||||
this.packetSize = this.loginData.packetSize;
|
||||
this.clientProgVer = 0;
|
||||
this.clientPid = process.pid;
|
||||
this.connectionId = 0;
|
||||
this.clientTimeZone = new Date().getTimezoneOffset();
|
||||
this.clientLcid = 0x00000409;
|
||||
this.flags1 = FLAGS_1.ENDIAN_LITTLE | FLAGS_1.CHARSET_ASCII | FLAGS_1.FLOAT_IEEE_754 | FLAGS_1.BCD_DUMPLOAD_OFF | FLAGS_1.USE_DB_OFF | FLAGS_1.SET_LANG_WARN_ON;
|
||||
if (this.loginData.initDbFatal) {
|
||||
this.flags1 |= FLAGS_1.INIT_DB_FATAL;
|
||||
} else {
|
||||
this.flags1 |= FLAGS_1.INIT_DB_WARN;
|
||||
}
|
||||
this.flags2 = FLAGS_2.INIT_LANG_WARN | FLAGS_2.ODBC_OFF | FLAGS_2.USER_NORMAL;
|
||||
if (this.loginData.domain) {
|
||||
this.flags2 |= FLAGS_2.INTEGRATED_SECURITY_ON;
|
||||
} else {
|
||||
this.flags2 |= FLAGS_2.INTEGRATED_SECURITY_OFF;
|
||||
}
|
||||
this.flags3 = FLAGS_3.CHANGE_PASSWORD_NO | FLAGS_3.UNKNOWN_COLLATION_HANDLING;
|
||||
this.typeFlags = TYPE_FLAGS.SQL_DFLT | TYPE_FLAGS.OLEDB_OFF;
|
||||
if (this.loginData.readOnlyIntent) {
|
||||
this.typeFlags |= TYPE_FLAGS.READ_ONLY_INTENT;
|
||||
} else {
|
||||
this.typeFlags |= TYPE_FLAGS.READ_WRITE_INTENT;
|
||||
}
|
||||
|
||||
var buffer = new WritableTrackingBuffer(100);
|
||||
buffer.writeUInt32LE(this.tdsVersion);
|
||||
buffer.writeUInt32LE(this.packetSize);
|
||||
buffer.writeUInt32LE(this.clientProgVer);
|
||||
buffer.writeUInt32LE(this.clientPid);
|
||||
buffer.writeUInt32LE(this.connectionId);
|
||||
buffer.writeUInt8(this.flags1);
|
||||
buffer.writeUInt8(this.flags2);
|
||||
buffer.writeUInt8(this.typeFlags);
|
||||
buffer.writeUInt8(this.flags3);
|
||||
buffer.writeInt32LE(this.clientTimeZone);
|
||||
buffer.writeUInt32LE(this.clientLcid);
|
||||
return buffer.data;
|
||||
}
|
||||
}, {
|
||||
key: 'createVariableData',
|
||||
value: function createVariableData(offset) {
|
||||
this.variableLengthsLength = 9 * 4 + 6 + 3 * 4 + 4;
|
||||
if (this.loginData.tdsVersion === '7_1') {
|
||||
this.variableLengthsLength = 9 * 4 + 6 + 2 * 4;
|
||||
}
|
||||
var variableData = {
|
||||
offsetsAndLengths: new WritableTrackingBuffer(200),
|
||||
data: new WritableTrackingBuffer(200, 'ucs2'),
|
||||
offset: offset + this.variableLengthsLength
|
||||
};
|
||||
this.hostname = os.hostname();
|
||||
this.loginData = this.loginData || {};
|
||||
this.loginData.appName = this.loginData.appName || 'Tedious';
|
||||
this.libraryName = libraryName;
|
||||
this.clientId = new Buffer([1, 2, 3, 4, 5, 6]);
|
||||
if (!this.loginData.domain) {
|
||||
this.sspi = '';
|
||||
this.sspiLong = 0;
|
||||
}
|
||||
this.attachDbFile = '';
|
||||
this.changePassword = '';
|
||||
this.addVariableDataString(variableData, this.hostname);
|
||||
this.addVariableDataString(variableData, this.loginData.userName);
|
||||
this.addVariableDataBuffer(variableData, this.createPasswordBuffer());
|
||||
this.addVariableDataString(variableData, this.loginData.appName);
|
||||
this.addVariableDataString(variableData, this.loginData.serverName);
|
||||
this.addVariableDataString(variableData, '');
|
||||
this.addVariableDataString(variableData, this.libraryName);
|
||||
this.addVariableDataString(variableData, this.loginData.language);
|
||||
this.addVariableDataString(variableData, this.loginData.database);
|
||||
variableData.offsetsAndLengths.writeBuffer(this.clientId);
|
||||
if (this.loginData.domain) {
|
||||
this.ntlmPacket = this.createNTLMRequest(this.loginData);
|
||||
this.sspiLong = this.ntlmPacket.length;
|
||||
variableData.offsetsAndLengths.writeUInt16LE(variableData.offset);
|
||||
variableData.offsetsAndLengths.writeUInt16LE(this.ntlmPacket.length);
|
||||
variableData.data.writeBuffer(this.ntlmPacket);
|
||||
variableData.offset += this.ntlmPacket.length;
|
||||
} else {
|
||||
this.addVariableDataString(variableData, this.sspi);
|
||||
}
|
||||
this.addVariableDataString(variableData, this.attachDbFile);
|
||||
if (this.loginData.tdsVersion > '7_1') {
|
||||
this.addVariableDataString(variableData, this.changePassword);
|
||||
variableData.offsetsAndLengths.writeUInt32LE(this.sspiLong);
|
||||
}
|
||||
return Buffer.concat([variableData.offsetsAndLengths.data, variableData.data.data]);
|
||||
}
|
||||
}, {
|
||||
key: 'addVariableDataBuffer',
|
||||
value: function addVariableDataBuffer(variableData, buffer) {
|
||||
variableData.offsetsAndLengths.writeUInt16LE(variableData.offset);
|
||||
variableData.offsetsAndLengths.writeUInt16LE(buffer.length / 2);
|
||||
variableData.data.writeBuffer(buffer);
|
||||
return variableData.offset += buffer.length;
|
||||
}
|
||||
}, {
|
||||
key: 'addVariableDataString',
|
||||
value: function addVariableDataString(variableData, value) {
|
||||
value || (value = '');
|
||||
variableData.offsetsAndLengths.writeUInt16LE(variableData.offset);
|
||||
variableData.offsetsAndLengths.writeUInt16LE(value.length);
|
||||
variableData.data.writeString(value);
|
||||
return variableData.offset += value.length * 2;
|
||||
}
|
||||
}, {
|
||||
key: 'createNTLMRequest',
|
||||
value: function createNTLMRequest(options) {
|
||||
var domain = escape(options.domain.toUpperCase());
|
||||
var workstation = options.workstation ? escape(options.workstation.toUpperCase()) : '';
|
||||
var protocol = 'NTLMSSP\u0000';
|
||||
var BODY_LENGTH = 40;
|
||||
var bufferLength = BODY_LENGTH + domain.length;
|
||||
var buffer = new WritableTrackingBuffer(bufferLength);
|
||||
|
||||
var type1flags = this.getNTLMFlags();
|
||||
if (workstation === '') {
|
||||
type1flags -= NTLMFlags.NTLM_NegotiateOemWorkstationSupplied;
|
||||
}
|
||||
|
||||
buffer.writeString(protocol, 'utf8');
|
||||
buffer.writeUInt32LE(1);
|
||||
buffer.writeUInt32LE(type1flags);
|
||||
buffer.writeUInt16LE(domain.length);
|
||||
buffer.writeUInt16LE(domain.length);
|
||||
buffer.writeUInt32LE(BODY_LENGTH + workstation.length);
|
||||
buffer.writeUInt16LE(workstation.length);
|
||||
buffer.writeUInt16LE(workstation.length);
|
||||
buffer.writeUInt32LE(BODY_LENGTH);
|
||||
buffer.writeUInt8(5);
|
||||
buffer.writeUInt8(0);
|
||||
buffer.writeUInt16LE(2195);
|
||||
buffer.writeUInt8(0);
|
||||
buffer.writeUInt8(0);
|
||||
buffer.writeUInt8(0);
|
||||
buffer.writeUInt8(15);
|
||||
buffer.writeString(workstation, 'ascii');
|
||||
buffer.writeString(domain, 'ascii');
|
||||
return buffer.data;
|
||||
}
|
||||
}, {
|
||||
key: 'createPasswordBuffer',
|
||||
value: function createPasswordBuffer() {
|
||||
var password = this.loginData.password || '';
|
||||
password = new Buffer(password, 'ucs2');
|
||||
for (var b = 0, len = password.length; b < len; b++) {
|
||||
var byte = password[b];
|
||||
var lowNibble = byte & 0x0f;
|
||||
var highNibble = byte >> 4;
|
||||
byte = lowNibble << 4 | highNibble;
|
||||
byte = byte ^ 0xa5;
|
||||
password[b] = byte;
|
||||
}
|
||||
return password;
|
||||
}
|
||||
}, {
|
||||
key: 'getNTLMFlags',
|
||||
value: function getNTLMFlags() {
|
||||
return NTLMFlags.NTLM_NegotiateUnicode + NTLMFlags.NTLM_NegotiateOEM + NTLMFlags.NTLM_RequestTarget + NTLMFlags.NTLM_NegotiateNTLM + NTLMFlags.NTLM_NegotiateOemDomainSupplied + NTLMFlags.NTLM_NegotiateOemWorkstationSupplied + NTLMFlags.NTLM_NegotiateAlwaysSign + NTLMFlags.NTLM_NegotiateVersion + NTLMFlags.NTLM_NegotiateExtendedSecurity + NTLMFlags.NTLM_Negotiate128 + NTLMFlags.NTLM_Negotiate56;
|
||||
}
|
||||
}, {
|
||||
key: 'toString',
|
||||
value: function toString(indent) {
|
||||
indent || (indent = '');
|
||||
return indent + 'Login7 - ' + sprintf('TDS:0x%08X, PacketSize:0x%08X, ClientProgVer:0x%08X, ClientPID:0x%08X, ConnectionID:0x%08X', this.tdsVersion, this.packetSize, this.clientProgVer, this.clientPid, this.connectionId) + '\n' + indent + ' ' + sprintf('Flags1:0x%02X, Flags2:0x%02X, TypeFlags:0x%02X, Flags3:0x%02X, ClientTimezone:%d, ClientLCID:0x%08X', this.flags1, this.flags2, this.typeFlags, this.flags3, this.clientTimeZone, this.clientLcid) + '\n' + indent + ' ' + sprintf("Hostname:'%s', Username:'%s', Password:'%s', AppName:'%s', ServerName:'%s', LibraryName:'%s'", this.hostname, this.loginData.userName, this.loginData.password, this.loginData.appName, this.loginData.serverName, libraryName) + '\n' + indent + ' ' + sprintf("Language:'%s', Database:'%s', SSPI:'%s', AttachDbFile:'%s', ChangePassword:'%s'", this.loginData.language, this.loginData.database, this.sspi, this.attachDbFile, this.changePassword);
|
||||
}
|
||||
}]);
|
||||
|
||||
return Login7Payload;
|
||||
})();
|
||||
@@ -1,205 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var _get = require('babel-runtime/helpers/get')['default'];
|
||||
|
||||
var _inherits = require('babel-runtime/helpers/inherits')['default'];
|
||||
|
||||
var _createClass = require('babel-runtime/helpers/create-class')['default'];
|
||||
|
||||
var _classCallCheck = require('babel-runtime/helpers/class-call-check')['default'];
|
||||
|
||||
var tls = require('tls');
|
||||
var crypto = require('crypto');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var Transform = require('readable-stream').Transform;
|
||||
|
||||
require('./buffertools');
|
||||
|
||||
var Packet = require('./packet').Packet;
|
||||
var TYPE = require('./packet').TYPE;
|
||||
var packetHeaderLength = require('./packet').HEADER_LENGTH;
|
||||
|
||||
var ReadablePacketStream = (function (_Transform) {
|
||||
_inherits(ReadablePacketStream, _Transform);
|
||||
|
||||
function ReadablePacketStream() {
|
||||
_classCallCheck(this, ReadablePacketStream);
|
||||
|
||||
_get(Object.getPrototypeOf(ReadablePacketStream.prototype), 'constructor', this).call(this, { objectMode: true });
|
||||
|
||||
this.buffer = new Buffer(0);
|
||||
this.position = 0;
|
||||
}
|
||||
|
||||
_createClass(ReadablePacketStream, [{
|
||||
key: '_transform',
|
||||
value: function _transform(chunk, encoding, callback) {
|
||||
if (this.position === this.buffer.length) {
|
||||
// If we have fully consumed the previous buffer,
|
||||
// we can just replace it with the new chunk
|
||||
this.buffer = chunk;
|
||||
} else {
|
||||
// If we haven't fully consumed the previous buffer,
|
||||
// we simply concatenate the leftovers and the new chunk.
|
||||
this.buffer = Buffer.concat([this.buffer.slice(this.position), chunk], this.buffer.length - this.position + chunk.length);
|
||||
}
|
||||
|
||||
this.position = 0;
|
||||
|
||||
// The packet header is always 8 bytes of length.
|
||||
while (this.buffer.length >= this.position + packetHeaderLength) {
|
||||
// Get the full packet length
|
||||
var _length = this.buffer.readUInt16BE(this.position + 2);
|
||||
|
||||
if (this.buffer.length >= this.position + _length) {
|
||||
var data = this.buffer.slice(this.position, this.position + _length);
|
||||
this.position += _length;
|
||||
this.push(new Packet(data));
|
||||
} else {
|
||||
// Not enough data to provide the next packet. Stop here and wait for
|
||||
// the next call to `_transform`.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
callback();
|
||||
}
|
||||
}]);
|
||||
|
||||
return ReadablePacketStream;
|
||||
})(Transform);
|
||||
|
||||
module.exports = (function (_EventEmitter) {
|
||||
_inherits(MessageIO, _EventEmitter);
|
||||
|
||||
function MessageIO(socket, _packetSize, debug) {
|
||||
var _this = this;
|
||||
|
||||
_classCallCheck(this, MessageIO);
|
||||
|
||||
_get(Object.getPrototypeOf(MessageIO.prototype), 'constructor', this).call(this);
|
||||
|
||||
this.socket = socket;
|
||||
this._packetSize = _packetSize;
|
||||
this.debug = debug;
|
||||
this.sendPacket = this.sendPacket.bind(this);
|
||||
|
||||
this.packetStream = new ReadablePacketStream();
|
||||
this.packetStream.on('data', function (packet) {
|
||||
_this.logPacket('Received', packet);
|
||||
_this.emit('data', packet.data());
|
||||
if (packet.isLast()) {
|
||||
_this.emit('message');
|
||||
}
|
||||
});
|
||||
|
||||
this.socket.pipe(this.packetStream);
|
||||
this.packetDataSize = this._packetSize - packetHeaderLength;
|
||||
}
|
||||
|
||||
_createClass(MessageIO, [{
|
||||
key: 'packetSize',
|
||||
value: function packetSize(_packetSize2) {
|
||||
if (arguments.length > 0) {
|
||||
this.debug.log('Packet size changed from ' + this._packetSize + ' to ' + _packetSize2);
|
||||
this._packetSize = _packetSize2;
|
||||
this.packetDataSize = this._packetSize - packetHeaderLength;
|
||||
}
|
||||
return this._packetSize;
|
||||
}
|
||||
}, {
|
||||
key: 'startTls',
|
||||
value: function startTls(credentialsDetails) {
|
||||
var _this2 = this;
|
||||
|
||||
var credentials = tls.createSecureContext ? tls.createSecureContext(credentialsDetails) : crypto.createCredentials(credentialsDetails);
|
||||
|
||||
this.securePair = tls.createSecurePair(credentials);
|
||||
this.tlsNegotiationComplete = false;
|
||||
|
||||
this.securePair.on('secure', function () {
|
||||
var cipher = _this2.securePair.cleartext.getCipher();
|
||||
_this2.debug.log('TLS negotiated (' + cipher.name + ', ' + cipher.version + ')');
|
||||
_this2.emit('secure', _this2.securePair.cleartext);
|
||||
_this2.encryptAllFutureTraffic();
|
||||
});
|
||||
|
||||
this.securePair.encrypted.on('data', function (data) {
|
||||
_this2.sendMessage(TYPE.PRELOGIN, data);
|
||||
});
|
||||
|
||||
// On Node >= 0.12, the encrypted stream automatically starts spewing out
|
||||
// data once we attach a `data` listener. But on Node <= 0.10.x, this is not
|
||||
// the case. We need to kick the cleartext stream once to get the
|
||||
// encrypted end of the secure pair to emit the TLS handshake data.
|
||||
this.securePair.cleartext.write('');
|
||||
}
|
||||
}, {
|
||||
key: 'encryptAllFutureTraffic',
|
||||
value: function encryptAllFutureTraffic() {
|
||||
this.socket.unpipe(this.packetStream);
|
||||
this.securePair.encrypted.removeAllListeners('data');
|
||||
this.socket.pipe(this.securePair.encrypted);
|
||||
this.securePair.encrypted.pipe(this.socket);
|
||||
this.securePair.cleartext.pipe(this.packetStream);
|
||||
this.tlsNegotiationComplete = true;
|
||||
}
|
||||
}, {
|
||||
key: 'tlsHandshakeData',
|
||||
value: function tlsHandshakeData(data) {
|
||||
this.securePair.encrypted.write(data);
|
||||
}
|
||||
|
||||
// TODO listen for 'drain' event when socket.write returns false.
|
||||
// TODO implement incomplete request cancelation (2.2.1.6)
|
||||
}, {
|
||||
key: 'sendMessage',
|
||||
value: function sendMessage(packetType, data, resetConnection) {
|
||||
var numberOfPackets = undefined;
|
||||
if (data) {
|
||||
numberOfPackets = Math.floor((data.length - 1) / this.packetDataSize) + 1;
|
||||
} else {
|
||||
numberOfPackets = 1;
|
||||
data = new Buffer(0);
|
||||
}
|
||||
|
||||
for (var packetNumber = 0; packetNumber < numberOfPackets; packetNumber++) {
|
||||
var payloadStart = packetNumber * this.packetDataSize;
|
||||
|
||||
var payloadEnd = undefined;
|
||||
if (packetNumber < numberOfPackets - 1) {
|
||||
payloadEnd = payloadStart + this.packetDataSize;
|
||||
} else {
|
||||
payloadEnd = data.length;
|
||||
}
|
||||
|
||||
var packetPayload = data.slice(payloadStart, payloadEnd);
|
||||
|
||||
var packet = new Packet(packetType);
|
||||
packet.last(packetNumber === numberOfPackets - 1);
|
||||
packet.resetConnection(resetConnection);
|
||||
packet.packetId(packetNumber + 1);
|
||||
packet.addData(packetPayload);
|
||||
this.sendPacket(packet);
|
||||
}
|
||||
}
|
||||
}, {
|
||||
key: 'sendPacket',
|
||||
value: function sendPacket(packet) {
|
||||
this.logPacket('Sent', packet);
|
||||
if (this.securePair && this.tlsNegotiationComplete) {
|
||||
this.securePair.cleartext.write(packet.buffer);
|
||||
} else {
|
||||
this.socket.write(packet.buffer);
|
||||
}
|
||||
}
|
||||
}, {
|
||||
key: 'logPacket',
|
||||
value: function logPacket(direction, packet) {
|
||||
this.debug.packet(direction, packet);
|
||||
return this.debug.data(packet);
|
||||
}
|
||||
}]);
|
||||
|
||||
return MessageIO;
|
||||
})(EventEmitter);
|
||||
@@ -1,177 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var codepageBySortId = require('./collation').codepageBySortId;
|
||||
var codepageByLcid = require('./collation').codepageByLcid;
|
||||
var TYPE = require('./data-type').TYPE;
|
||||
var sprintf = require('sprintf').sprintf;
|
||||
|
||||
module.exports = metadataParse;
|
||||
module.exports.readPrecision = readPrecision;
|
||||
module.exports.readScale = readScale;
|
||||
module.exports.readCollation = readCollation;
|
||||
|
||||
function readDataLength(parser, type, callback) {
|
||||
if ((type.id & 0x30) === 0x20) {
|
||||
// xx10xxxx - s2.2.4.2.1.3
|
||||
// Variable length
|
||||
if (type.dataLengthFromScale) {
|
||||
return callback(0); // dataLength is resolved from scale
|
||||
} else if (type.fixedDataLength) {
|
||||
return callback(type.fixedDataLength);
|
||||
}
|
||||
|
||||
switch (type.dataLengthLength) {
|
||||
case 0:
|
||||
return callback(undefined);
|
||||
|
||||
case 1:
|
||||
return parser.readUInt8(callback);
|
||||
|
||||
case 2:
|
||||
return parser.readUInt16LE(callback);
|
||||
|
||||
case 4:
|
||||
return parser.readUInt32LE(callback);
|
||||
|
||||
default:
|
||||
return parser.emit(new Error('Unsupported dataLengthLength ' + type.dataLengthLength + ' for data type ' + type.name));
|
||||
}
|
||||
} else {
|
||||
callback(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
function readPrecision(parser, type, callback) {
|
||||
if (type.hasPrecision) {
|
||||
parser.readUInt8(callback);
|
||||
} else {
|
||||
callback(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
function readScale(parser, type, callback) {
|
||||
if (type.hasScale) {
|
||||
parser.readUInt8(callback);
|
||||
} else {
|
||||
callback(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
function readCollation(parser, type, callback) {
|
||||
if (type.hasCollation) {
|
||||
// s2.2.5.1.2
|
||||
parser.readBuffer(5, function (collationData) {
|
||||
var collation = {};
|
||||
|
||||
collation.lcid = (collationData[2] & 0x0F) << 16;
|
||||
collation.lcid |= collationData[1] << 8;
|
||||
collation.lcid |= collationData[0];
|
||||
|
||||
// This may not be extracting the correct nibbles in the correct order.
|
||||
collation.flags = collationData[3] >> 4;
|
||||
collation.flags |= collationData[2] & 0xF0;
|
||||
|
||||
// This may not be extracting the correct nibble.
|
||||
collation.version = collationData[3] & 0x0F;
|
||||
|
||||
collation.sortId = collationData[4];
|
||||
|
||||
collation.codepage = codepageBySortId[collation.sortId] || codepageByLcid[collation.lcid] || 'CP1252';
|
||||
|
||||
callback(collation);
|
||||
});
|
||||
} else {
|
||||
callback(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
function readSchema(parser, type, callback) {
|
||||
if (type.hasSchemaPresent) {
|
||||
// s2.2.5.5.3
|
||||
parser.readUInt8(function (schemaPresent) {
|
||||
if (schemaPresent === 0x01) {
|
||||
parser.readBVarChar(function (dbname) {
|
||||
parser.readBVarChar(function (owningSchema) {
|
||||
parser.readUsVarChar(function (xmlSchemaCollection) {
|
||||
callback({
|
||||
dbname: dbname,
|
||||
owningSchema: owningSchema,
|
||||
xmlSchemaCollection: xmlSchemaCollection
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
callback(undefined);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
callback(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
function readUDTInfo(parser, type, callback) {
|
||||
if (type.hasUDTInfo) {
|
||||
parser.readUInt16LE(function (maxByteSize) {
|
||||
parser.readBVarChar(function (dbname) {
|
||||
parser.readBVarChar(function (owningSchema) {
|
||||
parser.readBVarChar(function (typeName) {
|
||||
parser.readUsVarChar(function (assemblyName) {
|
||||
callback({
|
||||
maxByteSize: maxByteSize,
|
||||
dbname: dbname,
|
||||
owningSchema: owningSchema,
|
||||
typeName: typeName,
|
||||
assemblyName: assemblyName
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
return callback();
|
||||
}
|
||||
}
|
||||
|
||||
function metadataParse(parser, options, callback) {
|
||||
(options.tdsVersion < '7_2' ? parser.readUInt16LE : parser.readUInt32LE).call(parser, function (userType) {
|
||||
parser.readUInt16LE(function (flags) {
|
||||
parser.readUInt8(function (typeNumber) {
|
||||
var type = TYPE[typeNumber];
|
||||
|
||||
if (!type) {
|
||||
return parser.emit(new Error(sprintf('Unrecognised data type 0x%02X', typeNumber)));
|
||||
}
|
||||
|
||||
readDataLength(parser, type, function (dataLength) {
|
||||
readPrecision(parser, type, function (precision) {
|
||||
readScale(parser, type, function (scale) {
|
||||
if (scale && type.dataLengthFromScale) {
|
||||
dataLength = type.dataLengthFromScale(scale);
|
||||
}
|
||||
|
||||
readCollation(parser, type, function (collation) {
|
||||
readSchema(parser, type, function (schema) {
|
||||
readUDTInfo(parser, type, function (udtInfo) {
|
||||
callback({
|
||||
userType: userType,
|
||||
flags: flags,
|
||||
type: type,
|
||||
collation: collation,
|
||||
precision: precision,
|
||||
scale: scale,
|
||||
dataLength: dataLength,
|
||||
schema: schema,
|
||||
udtInfo: udtInfo
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,190 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var _createClass = require('babel-runtime/helpers/create-class')['default'];
|
||||
|
||||
var _classCallCheck = require('babel-runtime/helpers/class-call-check')['default'];
|
||||
|
||||
var WritableTrackingBuffer = require('./tracking-buffer/writable-tracking-buffer');
|
||||
var crypto = require('crypto');
|
||||
var BigInteger = require('big-number').n;
|
||||
|
||||
var hex = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];
|
||||
|
||||
module.exports = (function () {
|
||||
function NTLMResponsePayload(loginData) {
|
||||
_classCallCheck(this, NTLMResponsePayload);
|
||||
|
||||
this.data = this.createResponse(loginData);
|
||||
}
|
||||
|
||||
_createClass(NTLMResponsePayload, [{
|
||||
key: 'toString',
|
||||
value: function toString(indent) {
|
||||
indent || (indent = '');
|
||||
return indent + 'NTLM Auth';
|
||||
}
|
||||
}, {
|
||||
key: 'createResponse',
|
||||
value: function createResponse(challenge) {
|
||||
var client_nonce = this.createClientNonce();
|
||||
var lmv2len = 24;
|
||||
var ntlmv2len = 16;
|
||||
var domain = challenge.domain;
|
||||
var username = challenge.userName;
|
||||
var password = challenge.password;
|
||||
var ntlmData = challenge.ntlmpacket;
|
||||
var server_data = ntlmData.target;
|
||||
var server_nonce = ntlmData.nonce;
|
||||
var bufferLength = 64 + domain.length * 2 + username.length * 2 + lmv2len + ntlmv2len + 8 + 8 + 8 + 4 + server_data.length + 4;
|
||||
var data = new WritableTrackingBuffer(bufferLength);
|
||||
data.position = 0;
|
||||
data.writeString('NTLMSSP\u0000', 'utf8');
|
||||
data.writeUInt32LE(0x03);
|
||||
var baseIdx = 64;
|
||||
var dnIdx = baseIdx;
|
||||
var unIdx = dnIdx + domain.length * 2;
|
||||
var l2Idx = unIdx + username.length * 2;
|
||||
var ntIdx = l2Idx + lmv2len;
|
||||
data.writeUInt16LE(lmv2len);
|
||||
data.writeUInt16LE(lmv2len);
|
||||
data.writeUInt32LE(l2Idx);
|
||||
data.writeUInt16LE(ntlmv2len);
|
||||
data.writeUInt16LE(ntlmv2len);
|
||||
data.writeUInt32LE(ntIdx);
|
||||
data.writeUInt16LE(domain.length * 2);
|
||||
data.writeUInt16LE(domain.length * 2);
|
||||
data.writeUInt32LE(dnIdx);
|
||||
data.writeUInt16LE(username.length * 2);
|
||||
data.writeUInt16LE(username.length * 2);
|
||||
data.writeUInt32LE(unIdx);
|
||||
data.writeUInt16LE(0);
|
||||
data.writeUInt16LE(0);
|
||||
data.writeUInt32LE(baseIdx);
|
||||
data.writeUInt16LE(0);
|
||||
data.writeUInt16LE(0);
|
||||
data.writeUInt32LE(baseIdx);
|
||||
data.writeUInt16LE(0x8201);
|
||||
data.writeUInt16LE(0x08);
|
||||
data.writeString(domain, 'ucs2');
|
||||
data.writeString(username, 'ucs2');
|
||||
var lmv2Data = this.lmv2Response(domain, username, password, server_nonce, client_nonce);
|
||||
data.copyFrom(lmv2Data);
|
||||
var genTime = new Date().getTime();
|
||||
ntlmData = this.ntlmv2Response(domain, username, password, server_nonce, server_data, client_nonce, genTime);
|
||||
data.copyFrom(ntlmData);
|
||||
data.writeUInt32LE(0x0101);
|
||||
data.writeUInt32LE(0x0000);
|
||||
var timestamp = this.createTimestamp(genTime);
|
||||
data.copyFrom(timestamp);
|
||||
data.copyFrom(client_nonce);
|
||||
data.writeUInt32LE(0x0000);
|
||||
data.copyFrom(server_data);
|
||||
data.writeUInt32LE(0x0000);
|
||||
return data.data;
|
||||
}
|
||||
}, {
|
||||
key: 'createClientNonce',
|
||||
value: function createClientNonce() {
|
||||
var client_nonce = new Buffer(8);
|
||||
var nidx = 0;
|
||||
while (nidx < 8) {
|
||||
client_nonce.writeUInt8(Math.ceil(Math.random() * 255), nidx);
|
||||
nidx++;
|
||||
}
|
||||
return client_nonce;
|
||||
}
|
||||
}, {
|
||||
key: 'ntlmv2Response',
|
||||
value: function ntlmv2Response(domain, user, password, serverNonce, targetInfo, clientNonce, mytime) {
|
||||
var timestamp = this.createTimestamp(mytime);
|
||||
var hash = this.ntv2Hash(domain, user, password);
|
||||
var dataLength = 40 + targetInfo.length;
|
||||
var data = new Buffer(dataLength);
|
||||
serverNonce.copy(data, 0, 0, 8);
|
||||
data.writeUInt32LE(0x101, 8);
|
||||
data.writeUInt32LE(0x0, 12);
|
||||
timestamp.copy(data, 16, 0, 8);
|
||||
clientNonce.copy(data, 24, 0, 8);
|
||||
data.writeUInt32LE(0x0, 32);
|
||||
targetInfo.copy(data, 36, 0, targetInfo.length);
|
||||
data.writeUInt32LE(0x0, 36 + targetInfo.length);
|
||||
return this.hmacMD5(data, hash);
|
||||
}
|
||||
}, {
|
||||
key: 'createTimestamp',
|
||||
value: function createTimestamp(time) {
|
||||
var tenthsOfAMicrosecond = new BigInteger(time).plus(11644473600).multiply(10000000);
|
||||
var hexArray = [];
|
||||
|
||||
var pair = [];
|
||||
while (tenthsOfAMicrosecond.val() !== '0') {
|
||||
var idx = tenthsOfAMicrosecond.mod(16);
|
||||
pair.unshift(hex[idx]);
|
||||
if (pair.length === 2) {
|
||||
hexArray.push(pair.join(''));
|
||||
pair = [];
|
||||
}
|
||||
}
|
||||
|
||||
if (pair.length > 0) {
|
||||
hexArray.push(pair[0] + '0');
|
||||
}
|
||||
|
||||
return new Buffer(hexArray.join(''), 'hex');
|
||||
}
|
||||
}, {
|
||||
key: 'lmv2Response',
|
||||
value: function lmv2Response(domain, user, password, serverNonce, clientNonce) {
|
||||
var hash = this.ntv2Hash(domain, user, password);
|
||||
var data = new Buffer(serverNonce.length + clientNonce.length);
|
||||
|
||||
serverNonce.copy(data);
|
||||
clientNonce.copy(data, serverNonce.length, 0, clientNonce.length);
|
||||
|
||||
var newhash = this.hmacMD5(data, hash);
|
||||
var response = new Buffer(newhash.length + clientNonce.length);
|
||||
|
||||
newhash.copy(response);
|
||||
clientNonce.copy(response, newhash.length, 0, clientNonce.length);
|
||||
|
||||
return response;
|
||||
}
|
||||
}, {
|
||||
key: 'ntv2Hash',
|
||||
value: function ntv2Hash(domain, user, password) {
|
||||
var hash = this.ntHash(password);
|
||||
var identity = new Buffer(user.toUpperCase() + domain.toUpperCase(), 'ucs2');
|
||||
return this.hmacMD5(identity, hash);
|
||||
}
|
||||
}, {
|
||||
key: 'ntHash',
|
||||
value: function ntHash(text) {
|
||||
var hash = new Buffer(21);
|
||||
hash.fill(0);
|
||||
|
||||
var unicodeString = new Buffer(text, 'ucs2');
|
||||
var md4 = crypto.createHash('md4').update(unicodeString).digest();
|
||||
if (md4.copy) {
|
||||
md4.copy(hash);
|
||||
} else {
|
||||
new Buffer(md4, 'ascii').copy(hash);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
}, {
|
||||
key: 'hmacMD5',
|
||||
value: function hmacMD5(data, key) {
|
||||
var hmac = crypto.createHmac('MD5', key);
|
||||
hmac.update(data);
|
||||
|
||||
var result = hmac.digest();
|
||||
if (result.copy) {
|
||||
return result;
|
||||
} else {
|
||||
return new Buffer(result, 'ascii').slice(0, 16);
|
||||
}
|
||||
}
|
||||
}]);
|
||||
|
||||
return NTLMResponsePayload;
|
||||
})();
|
||||
@@ -1,245 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var _createClass = require('babel-runtime/helpers/create-class')['default'];
|
||||
|
||||
var _classCallCheck = require('babel-runtime/helpers/class-call-check')['default'];
|
||||
|
||||
require('./buffertools');
|
||||
|
||||
var sprintf = require('sprintf').sprintf;
|
||||
|
||||
var HEADER_LENGTH = module.exports.HEADER_LENGTH = 8;
|
||||
|
||||
var TYPE = module.exports.TYPE = {
|
||||
SQL_BATCH: 0x01,
|
||||
RPC_REQUEST: 0x03,
|
||||
TABULAR_RESULT: 0x04,
|
||||
ATTENTION: 0x06,
|
||||
BULK_LOAD: 0x07,
|
||||
TRANSACTION_MANAGER: 0x0E,
|
||||
LOGIN7: 0x10,
|
||||
NTLMAUTH_PKT: 0x11,
|
||||
PRELOGIN: 0x12
|
||||
};
|
||||
|
||||
var typeByValue = {};
|
||||
|
||||
for (var _name in TYPE) {
|
||||
typeByValue[TYPE[_name]] = _name;
|
||||
}
|
||||
|
||||
var STATUS = {
|
||||
NORMAL: 0x00,
|
||||
EOM: 0x01,
|
||||
IGNORE: 0x02,
|
||||
RESETCONNECTION: 0x08,
|
||||
RESETCONNECTIONSKIPTRAN: 0x10
|
||||
};
|
||||
|
||||
var OFFSET = module.exports.OFFSET = {
|
||||
Type: 0,
|
||||
Status: 1,
|
||||
Length: 2,
|
||||
SPID: 4,
|
||||
PacketID: 6,
|
||||
Window: 7
|
||||
};
|
||||
|
||||
var DEFAULT_SPID = 0;
|
||||
|
||||
var DEFAULT_PACKETID = 1;
|
||||
|
||||
var DEFAULT_WINDOW = 0;
|
||||
|
||||
var NL = '\n';
|
||||
|
||||
var Packet = (function () {
|
||||
function Packet(typeOrBuffer) {
|
||||
_classCallCheck(this, Packet);
|
||||
|
||||
if (typeOrBuffer instanceof Buffer) {
|
||||
this.buffer = typeOrBuffer;
|
||||
} else {
|
||||
var type = typeOrBuffer;
|
||||
this.buffer = new Buffer(HEADER_LENGTH);
|
||||
this.buffer.writeUInt8(type, OFFSET.Type);
|
||||
this.buffer.writeUInt8(STATUS.NORMAL, OFFSET.Status);
|
||||
this.buffer.writeUInt16BE(DEFAULT_SPID, OFFSET.SPID);
|
||||
this.buffer.writeUInt8(DEFAULT_PACKETID, OFFSET.PacketID);
|
||||
this.buffer.writeUInt8(DEFAULT_WINDOW, OFFSET.Window);
|
||||
this.setLength();
|
||||
}
|
||||
}
|
||||
|
||||
_createClass(Packet, [{
|
||||
key: 'setLength',
|
||||
value: function setLength() {
|
||||
return this.buffer.writeUInt16BE(this.buffer.length, OFFSET.Length);
|
||||
}
|
||||
}, {
|
||||
key: 'length',
|
||||
value: function length() {
|
||||
return this.buffer.readUInt16BE(OFFSET.Length);
|
||||
}
|
||||
}, {
|
||||
key: 'resetConnection',
|
||||
value: function resetConnection(reset) {
|
||||
var status = this.buffer.readUInt8(OFFSET.Status);
|
||||
if (reset) {
|
||||
status |= STATUS.RESETCONNECTION;
|
||||
} else {
|
||||
status &= 0xFF - STATUS.RESETCONNECTION;
|
||||
}
|
||||
return this.buffer.writeUInt8(status, OFFSET.Status);
|
||||
}
|
||||
}, {
|
||||
key: 'last',
|
||||
value: function last(_last) {
|
||||
var status = this.buffer.readUInt8(OFFSET.Status);
|
||||
if (arguments.length > 0) {
|
||||
if (_last) {
|
||||
status |= STATUS.EOM;
|
||||
} else {
|
||||
status &= 0xFF - STATUS.EOM;
|
||||
}
|
||||
this.buffer.writeUInt8(status, OFFSET.Status);
|
||||
}
|
||||
return this.isLast();
|
||||
}
|
||||
}, {
|
||||
key: 'isLast',
|
||||
value: function isLast() {
|
||||
return !!(this.buffer.readUInt8(OFFSET.Status) & STATUS.EOM);
|
||||
}
|
||||
}, {
|
||||
key: 'packetId',
|
||||
value: function packetId(_packetId) {
|
||||
if (_packetId) {
|
||||
this.buffer.writeUInt8(_packetId % 256, OFFSET.PacketID);
|
||||
}
|
||||
return this.buffer.readUInt8(OFFSET.PacketID);
|
||||
}
|
||||
}, {
|
||||
key: 'addData',
|
||||
value: function addData(data) {
|
||||
this.buffer = Buffer.concat([this.buffer, data]);
|
||||
this.setLength();
|
||||
return this;
|
||||
}
|
||||
}, {
|
||||
key: 'data',
|
||||
value: function data() {
|
||||
return this.buffer.slice(HEADER_LENGTH);
|
||||
}
|
||||
}, {
|
||||
key: 'type',
|
||||
value: function type() {
|
||||
return this.buffer.readUInt8(OFFSET.Type);
|
||||
}
|
||||
}, {
|
||||
key: 'statusAsString',
|
||||
value: function statusAsString() {
|
||||
var status = this.buffer.readUInt8(OFFSET.Status);
|
||||
var statuses = [];
|
||||
|
||||
for (var _name2 in STATUS) {
|
||||
var value = STATUS[_name2];
|
||||
|
||||
if (status & value) {
|
||||
statuses.push(_name2);
|
||||
} else {
|
||||
statuses.push(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
return statuses.join(' ').trim();
|
||||
}
|
||||
}, {
|
||||
key: 'headerToString',
|
||||
value: function headerToString(indent) {
|
||||
indent || (indent = '');
|
||||
var text = sprintf('type:0x%02X(%s), status:0x%02X(%s), length:0x%04X, spid:0x%04X, packetId:0x%02X, window:0x%02X', this.buffer.readUInt8(OFFSET.Type), typeByValue[this.buffer.readUInt8(OFFSET.Type)], this.buffer.readUInt8(OFFSET.Status), this.statusAsString(), this.buffer.readUInt16BE(OFFSET.Length), this.buffer.readUInt16BE(OFFSET.SPID), this.buffer.readUInt8(OFFSET.PacketID), this.buffer.readUInt8(OFFSET.Window));
|
||||
return indent + text;
|
||||
}
|
||||
}, {
|
||||
key: 'dataToString',
|
||||
value: function dataToString(indent) {
|
||||
indent || (indent = '');
|
||||
|
||||
var BYTES_PER_GROUP = 0x04;
|
||||
var CHARS_PER_GROUP = 0x08;
|
||||
var BYTES_PER_LINE = 0x20;
|
||||
var data = this.data();
|
||||
|
||||
var dataDump = '';
|
||||
var chars = '';
|
||||
|
||||
for (var offset = 0; offset < data.length; offset++) {
|
||||
if (offset % BYTES_PER_LINE === 0) {
|
||||
dataDump += indent;
|
||||
dataDump += sprintf('%04X ', offset);
|
||||
}
|
||||
|
||||
if (data[offset] < 0x20 || data[offset] > 0x7E) {
|
||||
chars += '.';
|
||||
if ((offset + 1) % CHARS_PER_GROUP === 0 && !((offset + 1) % BYTES_PER_LINE === 0)) {
|
||||
chars += ' ';
|
||||
}
|
||||
} else {
|
||||
chars += String.fromCharCode(data[offset]);
|
||||
}
|
||||
|
||||
if (data[offset] != null) {
|
||||
dataDump += sprintf('%02X', data[offset]);
|
||||
}
|
||||
|
||||
if ((offset + 1) % BYTES_PER_GROUP === 0 && !((offset + 1) % BYTES_PER_LINE === 0)) {
|
||||
dataDump += ' ';
|
||||
}
|
||||
|
||||
if ((offset + 1) % BYTES_PER_LINE === 0) {
|
||||
dataDump += ' ' + chars;
|
||||
chars = '';
|
||||
if (offset < data.length - 1) {
|
||||
dataDump += NL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (chars.length) {
|
||||
dataDump += ' ' + chars;
|
||||
}
|
||||
|
||||
return dataDump;
|
||||
}
|
||||
}, {
|
||||
key: 'toString',
|
||||
value: function toString(indent) {
|
||||
indent || (indent = '');
|
||||
return this.headerToString(indent) + '\n' + this.dataToString(indent + indent);
|
||||
}
|
||||
}, {
|
||||
key: 'payloadString',
|
||||
value: function payloadString() {
|
||||
return '';
|
||||
}
|
||||
}]);
|
||||
|
||||
return Packet;
|
||||
})();
|
||||
|
||||
module.exports.Packet = Packet;
|
||||
|
||||
module.exports.isPacketComplete = isPacketComplete;
|
||||
function isPacketComplete(potentialPacketBuffer) {
|
||||
if (potentialPacketBuffer.length < HEADER_LENGTH) {
|
||||
return false;
|
||||
} else {
|
||||
return potentialPacketBuffer.length >= potentialPacketBuffer.readUInt16BE(OFFSET.Length);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.packetLength = packetLength;
|
||||
function packetLength(potentialPacketBuffer) {
|
||||
return potentialPacketBuffer.readUInt16BE(OFFSET.Length);
|
||||
}
|
||||
@@ -1,220 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var _createClass = require('babel-runtime/helpers/create-class')['default'];
|
||||
|
||||
var _classCallCheck = require('babel-runtime/helpers/class-call-check')['default'];
|
||||
|
||||
var sprintf = require('sprintf').sprintf;
|
||||
var WritableTrackingBuffer = require('./tracking-buffer/tracking-buffer').WritableTrackingBuffer;
|
||||
|
||||
var optionBufferSize = 20;
|
||||
|
||||
var VERSION = 0x000000001;
|
||||
|
||||
var SUBBUILD = 0x0001;
|
||||
|
||||
var TOKEN = {
|
||||
VERSION: 0x00,
|
||||
ENCRYPTION: 0x01,
|
||||
INSTOPT: 0x02,
|
||||
THREADID: 0x03,
|
||||
MARS: 0x04,
|
||||
TERMINATOR: 0xFF
|
||||
};
|
||||
|
||||
var ENCRYPT = {
|
||||
OFF: 0x00,
|
||||
ON: 0x01,
|
||||
NOT_SUP: 0x02,
|
||||
REQ: 0x03
|
||||
};
|
||||
|
||||
var encryptByValue = {};
|
||||
|
||||
for (var _name in ENCRYPT) {
|
||||
var value = ENCRYPT[_name];
|
||||
encryptByValue[value] = _name;
|
||||
}
|
||||
|
||||
var MARS = {
|
||||
OFF: 0x00,
|
||||
ON: 0x01
|
||||
};
|
||||
|
||||
var marsByValue = {};
|
||||
|
||||
for (var _name2 in MARS) {
|
||||
var value = MARS[_name2];
|
||||
marsByValue[value] = _name2;
|
||||
}
|
||||
|
||||
/*
|
||||
s2.2.6.4
|
||||
*/
|
||||
module.exports = (function () {
|
||||
function PreloginPayload(bufferOrOptions) {
|
||||
_classCallCheck(this, PreloginPayload);
|
||||
|
||||
if (bufferOrOptions instanceof Buffer) {
|
||||
this.data = bufferOrOptions;
|
||||
} else {
|
||||
this.options = bufferOrOptions || {};
|
||||
this.createOptions();
|
||||
}
|
||||
this.extractOptions();
|
||||
}
|
||||
|
||||
_createClass(PreloginPayload, [{
|
||||
key: 'createOptions',
|
||||
value: function createOptions() {
|
||||
var options = [this.createVersionOption(), this.createEncryptionOption(), this.createInstanceOption(), this.createThreadIdOption(), this.createMarsOption()];
|
||||
|
||||
var length = 0;
|
||||
for (var i = 0, len = options.length; i < len; i++) {
|
||||
var option = options[i];
|
||||
length += 5 + option.data.length;
|
||||
}
|
||||
length++; // terminator
|
||||
this.data = new Buffer(length);
|
||||
var optionOffset = 0;
|
||||
var optionDataOffset = 5 * options.length + 1;
|
||||
|
||||
for (var j = 0, len = options.length; j < len; j++) {
|
||||
var option = options[j];
|
||||
this.data.writeUInt8(option.token, optionOffset + 0);
|
||||
this.data.writeUInt16BE(optionDataOffset, optionOffset + 1);
|
||||
this.data.writeUInt16BE(option.data.length, optionOffset + 3);
|
||||
optionOffset += 5;
|
||||
option.data.copy(this.data, optionDataOffset);
|
||||
optionDataOffset += option.data.length;
|
||||
}
|
||||
|
||||
return this.data.writeUInt8(TOKEN.TERMINATOR, optionOffset);
|
||||
}
|
||||
}, {
|
||||
key: 'createVersionOption',
|
||||
value: function createVersionOption() {
|
||||
var buffer = new WritableTrackingBuffer(optionBufferSize);
|
||||
buffer.writeUInt32BE(VERSION);
|
||||
buffer.writeUInt16BE(SUBBUILD);
|
||||
return {
|
||||
token: TOKEN.VERSION,
|
||||
data: buffer.data
|
||||
};
|
||||
}
|
||||
}, {
|
||||
key: 'createEncryptionOption',
|
||||
value: function createEncryptionOption() {
|
||||
var buffer = new WritableTrackingBuffer(optionBufferSize);
|
||||
if (this.options.encrypt) {
|
||||
buffer.writeUInt8(ENCRYPT.ON);
|
||||
} else {
|
||||
buffer.writeUInt8(ENCRYPT.NOT_SUP);
|
||||
}
|
||||
return {
|
||||
token: TOKEN.ENCRYPTION,
|
||||
data: buffer.data
|
||||
};
|
||||
}
|
||||
}, {
|
||||
key: 'createInstanceOption',
|
||||
value: function createInstanceOption() {
|
||||
var buffer = new WritableTrackingBuffer(optionBufferSize);
|
||||
buffer.writeUInt8(0x00);
|
||||
return {
|
||||
token: TOKEN.INSTOPT,
|
||||
data: buffer.data
|
||||
};
|
||||
}
|
||||
}, {
|
||||
key: 'createThreadIdOption',
|
||||
value: function createThreadIdOption() {
|
||||
var buffer = new WritableTrackingBuffer(optionBufferSize);
|
||||
buffer.writeUInt32BE(0x00);
|
||||
return {
|
||||
token: TOKEN.THREADID,
|
||||
data: buffer.data
|
||||
};
|
||||
}
|
||||
}, {
|
||||
key: 'createMarsOption',
|
||||
value: function createMarsOption() {
|
||||
var buffer = new WritableTrackingBuffer(optionBufferSize);
|
||||
buffer.writeUInt8(MARS.OFF);
|
||||
return {
|
||||
token: TOKEN.MARS,
|
||||
data: buffer.data
|
||||
};
|
||||
}
|
||||
}, {
|
||||
key: 'extractOptions',
|
||||
value: function extractOptions() {
|
||||
var offset = 0;
|
||||
while (this.data[offset] !== TOKEN.TERMINATOR) {
|
||||
var dataOffset = this.data.readUInt16BE(offset + 1);
|
||||
var dataLength = this.data.readUInt16BE(offset + 3);
|
||||
switch (this.data[offset]) {
|
||||
case TOKEN.VERSION:
|
||||
this.extractVersion(dataOffset);
|
||||
break;
|
||||
case TOKEN.ENCRYPTION:
|
||||
this.extractEncryption(dataOffset);
|
||||
break;
|
||||
case TOKEN.INSTOPT:
|
||||
this.extractInstance(dataOffset);
|
||||
break;
|
||||
case TOKEN.THREADID:
|
||||
if (dataLength > 0) {
|
||||
this.extractThreadId(dataOffset);
|
||||
}
|
||||
break;
|
||||
case TOKEN.MARS:
|
||||
this.extractMars(dataOffset);
|
||||
}
|
||||
offset += 5;
|
||||
dataOffset += dataLength;
|
||||
}
|
||||
}
|
||||
}, {
|
||||
key: 'extractVersion',
|
||||
value: function extractVersion(offset) {
|
||||
return this.version = {
|
||||
major: this.data.readUInt8(offset + 0),
|
||||
minor: this.data.readUInt8(offset + 1),
|
||||
patch: this.data.readUInt8(offset + 2),
|
||||
trivial: this.data.readUInt8(offset + 3),
|
||||
subbuild: this.data.readUInt16BE(offset + 4)
|
||||
};
|
||||
}
|
||||
}, {
|
||||
key: 'extractEncryption',
|
||||
value: function extractEncryption(offset) {
|
||||
this.encryption = this.data.readUInt8(offset);
|
||||
return this.encryptionString = encryptByValue[this.encryption];
|
||||
}
|
||||
}, {
|
||||
key: 'extractInstance',
|
||||
value: function extractInstance(offset) {
|
||||
return this.instance = this.data.readUInt8(offset);
|
||||
}
|
||||
}, {
|
||||
key: 'extractThreadId',
|
||||
value: function extractThreadId(offset) {
|
||||
return this.threadId = this.data.readUInt32BE(offset);
|
||||
}
|
||||
}, {
|
||||
key: 'extractMars',
|
||||
value: function extractMars(offset) {
|
||||
this.mars = this.data.readUInt8(offset);
|
||||
return this.marsString = marsByValue[this.mars];
|
||||
}
|
||||
}, {
|
||||
key: 'toString',
|
||||
value: function toString(indent) {
|
||||
indent || (indent = '');
|
||||
return indent + 'PreLogin - ' + sprintf('version:%d.%d.%d.%d %d, encryption:0x%02X(%s), instopt:0x%02X, threadId:0x%08X, mars:0x%02X(%s)', this.version.major, this.version.minor, this.version.patch, this.version.trivial, this.version.subbuild, this.encryption ? this.encryption : 0, this.encryptionString ? this.encryptionString : 0, this.instance ? this.instance : 0, this.threadId ? this.threadId : 0, this.mars ? this.mars : 0, this.marsString ? this.marsString : 0);
|
||||
}
|
||||
}]);
|
||||
|
||||
return PreloginPayload;
|
||||
})();
|
||||
@@ -1,165 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var _get = require('babel-runtime/helpers/get')['default'];
|
||||
|
||||
var _inherits = require('babel-runtime/helpers/inherits')['default'];
|
||||
|
||||
var _createClass = require('babel-runtime/helpers/create-class')['default'];
|
||||
|
||||
var _classCallCheck = require('babel-runtime/helpers/class-call-check')['default'];
|
||||
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var TYPES = require('./data-type').typeByName;
|
||||
var RequestError = require('./errors').RequestError;
|
||||
|
||||
module.exports = (function (_EventEmitter) {
|
||||
_inherits(Request, _EventEmitter);
|
||||
|
||||
function Request(sqlTextOrProcedure, callback) {
|
||||
_classCallCheck(this, Request);
|
||||
|
||||
_get(Object.getPrototypeOf(Request.prototype), 'constructor', this).call(this);
|
||||
|
||||
this.sqlTextOrProcedure = sqlTextOrProcedure;
|
||||
this.callback = callback;
|
||||
this.parameters = [];
|
||||
this.parametersByName = {};
|
||||
this.userCallback = this.callback;
|
||||
this.callback = function () {
|
||||
if (this.preparing) {
|
||||
this.emit('prepared');
|
||||
return this.preparing = false;
|
||||
} else {
|
||||
this.userCallback.apply(this, arguments);
|
||||
return this.emit('requestCompleted');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
_createClass(Request, [{
|
||||
key: 'addParameter',
|
||||
value: function addParameter(name, type, value, options) {
|
||||
if (options == null) {
|
||||
options = {};
|
||||
}
|
||||
|
||||
var parameter = {
|
||||
type: type,
|
||||
name: name,
|
||||
value: value,
|
||||
output: options.output || (options.output = false),
|
||||
length: options.length,
|
||||
precision: options.precision,
|
||||
scale: options.scale
|
||||
};
|
||||
this.parameters.push(parameter);
|
||||
return this.parametersByName[name] = parameter;
|
||||
}
|
||||
}, {
|
||||
key: 'addOutputParameter',
|
||||
value: function addOutputParameter(name, type, value, options) {
|
||||
if (options == null) {
|
||||
options = {};
|
||||
}
|
||||
options.output = true;
|
||||
return this.addParameter(name, type, value, options);
|
||||
}
|
||||
}, {
|
||||
key: 'makeParamsParameter',
|
||||
value: function makeParamsParameter(parameters) {
|
||||
var paramsParameter = '';
|
||||
for (var i = 0, len = parameters.length; i < len; i++) {
|
||||
var parameter = parameters[i];
|
||||
if (paramsParameter.length > 0) {
|
||||
paramsParameter += ', ';
|
||||
}
|
||||
paramsParameter += '@' + parameter.name + ' ';
|
||||
paramsParameter += parameter.type.declaration(parameter);
|
||||
if (parameter.output) {
|
||||
paramsParameter += ' OUTPUT';
|
||||
}
|
||||
}
|
||||
return paramsParameter;
|
||||
}
|
||||
}, {
|
||||
key: 'transformIntoExecuteSqlRpc',
|
||||
value: function transformIntoExecuteSqlRpc() {
|
||||
if (this.validateParameters()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.originalParameters = this.parameters;
|
||||
this.parameters = [];
|
||||
this.addParameter('statement', TYPES.NVarChar, this.sqlTextOrProcedure);
|
||||
if (this.originalParameters.length) {
|
||||
this.addParameter('params', TYPES.NVarChar, this.makeParamsParameter(this.originalParameters));
|
||||
}
|
||||
|
||||
for (var i = 0, len = this.originalParameters.length; i < len; i++) {
|
||||
var parameter = this.originalParameters[i];
|
||||
this.parameters.push(parameter);
|
||||
}
|
||||
return this.sqlTextOrProcedure = 'sp_executesql';
|
||||
}
|
||||
}, {
|
||||
key: 'transformIntoPrepareRpc',
|
||||
value: function transformIntoPrepareRpc() {
|
||||
var _this = this;
|
||||
|
||||
this.originalParameters = this.parameters;
|
||||
this.parameters = [];
|
||||
this.addOutputParameter('handle', TYPES.Int);
|
||||
this.addParameter('params', TYPES.NVarChar, this.makeParamsParameter(this.originalParameters));
|
||||
this.addParameter('stmt', TYPES.NVarChar, this.sqlTextOrProcedure);
|
||||
this.sqlTextOrProcedure = 'sp_prepare';
|
||||
this.preparing = true;
|
||||
return this.on('returnValue', function (name, value) {
|
||||
if (name === 'handle') {
|
||||
return _this.handle = value;
|
||||
} else {
|
||||
return _this.error = RequestError('Tedious > Unexpected output parameter ' + name + ' from sp_prepare');
|
||||
}
|
||||
});
|
||||
}
|
||||
}, {
|
||||
key: 'transformIntoUnprepareRpc',
|
||||
value: function transformIntoUnprepareRpc() {
|
||||
this.parameters = [];
|
||||
this.addParameter('handle', TYPES.Int, this.handle);
|
||||
return this.sqlTextOrProcedure = 'sp_unprepare';
|
||||
}
|
||||
}, {
|
||||
key: 'transformIntoExecuteRpc',
|
||||
value: function transformIntoExecuteRpc(parameters) {
|
||||
this.parameters = [];
|
||||
this.addParameter('handle', TYPES.Int, this.handle);
|
||||
|
||||
for (var i = 0, len = this.originalParameters.length; i < len; i++) {
|
||||
var parameter = this.originalParameters[i];
|
||||
parameter.value = parameters[parameter.name];
|
||||
this.parameters.push(parameter);
|
||||
}
|
||||
|
||||
if (this.validateParameters()) {
|
||||
return;
|
||||
}
|
||||
|
||||
return this.sqlTextOrProcedure = 'sp_execute';
|
||||
}
|
||||
}, {
|
||||
key: 'validateParameters',
|
||||
value: function validateParameters() {
|
||||
for (var i = 0, len = this.parameters.length; i < len; i++) {
|
||||
var parameter = this.parameters[i];
|
||||
var value = parameter.type.validate(parameter.value);
|
||||
if (value instanceof TypeError) {
|
||||
return this.error = new RequestError('Validation failed for parameter \'' + parameter.name + '\'. ' + value.message, 'EPARAM');
|
||||
}
|
||||
parameter.value = value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}]);
|
||||
|
||||
return Request;
|
||||
})(EventEmitter);
|
||||
@@ -1,104 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var _createClass = require('babel-runtime/helpers/create-class')['default'];
|
||||
|
||||
var _classCallCheck = require('babel-runtime/helpers/class-call-check')['default'];
|
||||
|
||||
var WritableTrackingBuffer = require('./tracking-buffer/tracking-buffer').WritableTrackingBuffer;
|
||||
var writeAllHeaders = require('./all-headers').writeToTrackingBuffer;
|
||||
|
||||
// const OPTION = {
|
||||
// WITH_RECOMPILE: 0x01,
|
||||
// NO_METADATA: 0x02,
|
||||
// REUSE_METADATA: 0x04
|
||||
// };
|
||||
|
||||
var STATUS = {
|
||||
BY_REF_VALUE: 0x01,
|
||||
DEFAULT_VALUE: 0x02
|
||||
};
|
||||
|
||||
/*
|
||||
s2.2.6.5
|
||||
*/
|
||||
module.exports = (function () {
|
||||
function RpcRequestPayload(request, txnDescriptor, options) {
|
||||
_classCallCheck(this, RpcRequestPayload);
|
||||
|
||||
this.request = request;
|
||||
this.procedure = this.request.sqlTextOrProcedure;
|
||||
|
||||
var buffer = new WritableTrackingBuffer(500);
|
||||
if (options.tdsVersion >= '7_2') {
|
||||
var outstandingRequestCount = 1;
|
||||
writeAllHeaders(buffer, txnDescriptor, outstandingRequestCount);
|
||||
}
|
||||
|
||||
if (typeof this.procedure === 'string') {
|
||||
buffer.writeUsVarchar(this.procedure);
|
||||
} else {
|
||||
buffer.writeUShort(0xFFFF);
|
||||
buffer.writeUShort(this.procedure);
|
||||
}
|
||||
|
||||
var optionFlags = 0;
|
||||
buffer.writeUInt16LE(optionFlags);
|
||||
|
||||
var parameters = this.request.parameters;
|
||||
for (var i = 0, len = parameters.length; i < len; i++) {
|
||||
var parameter = parameters[i];
|
||||
buffer.writeBVarchar('@' + parameter.name);
|
||||
|
||||
var statusFlags = 0;
|
||||
if (parameter.output) {
|
||||
statusFlags |= STATUS.BY_REF_VALUE;
|
||||
}
|
||||
buffer.writeUInt8(statusFlags);
|
||||
|
||||
var param = {
|
||||
value: parameter.value
|
||||
};
|
||||
|
||||
var type = parameter.type;
|
||||
|
||||
if ((type.id & 0x30) === 0x20) {
|
||||
if (parameter.length) {
|
||||
param.length = parameter.length;
|
||||
} else if (type.resolveLength) {
|
||||
param.length = type.resolveLength(parameter);
|
||||
}
|
||||
}
|
||||
|
||||
if (type.hasPrecision) {
|
||||
if (parameter.precision) {
|
||||
param.precision = parameter.precision;
|
||||
} else if (type.resolvePrecision) {
|
||||
param.precision = type.resolvePrecision(parameter);
|
||||
}
|
||||
}
|
||||
|
||||
if (type.hasScale) {
|
||||
if (parameter.scale) {
|
||||
param.scale = parameter.scale;
|
||||
} else if (type.resolveScale) {
|
||||
param.scale = type.resolveScale(parameter);
|
||||
}
|
||||
}
|
||||
|
||||
type.writeTypeInfo(buffer, param, options);
|
||||
type.writeParameterData(buffer, param, options);
|
||||
}
|
||||
|
||||
this.data = buffer.data;
|
||||
}
|
||||
|
||||
_createClass(RpcRequestPayload, [{
|
||||
key: 'toString',
|
||||
value: function toString(indent) {
|
||||
indent || (indent = '');
|
||||
return indent + ('RPC Request - ' + this.procedure);
|
||||
}
|
||||
}]);
|
||||
|
||||
return RpcRequestPayload;
|
||||
})();
|
||||
@@ -1,19 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
Sp_Cursor: 1,
|
||||
Sp_CursorOpen: 2,
|
||||
Sp_CursorPrepare: 3,
|
||||
Sp_CursorExecute: 4,
|
||||
Sp_CursorPrepExec: 5,
|
||||
Sp_CursorUnprepare: 6,
|
||||
Sp_CursorFetch: 7,
|
||||
Sp_CursorOption: 8,
|
||||
Sp_CursorClose: 9,
|
||||
Sp_ExecuteSql: 10,
|
||||
Sp_Prepare: 11,
|
||||
Sp_Execute: 12,
|
||||
Sp_PrepExec: 13,
|
||||
Sp_PrepExecRpc: 14,
|
||||
Sp_Unprepare: 15
|
||||
};
|
||||
@@ -1,37 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var _createClass = require('babel-runtime/helpers/create-class')['default'];
|
||||
|
||||
var _classCallCheck = require('babel-runtime/helpers/class-call-check')['default'];
|
||||
|
||||
var WritableTrackingBuffer = require('./tracking-buffer/tracking-buffer').WritableTrackingBuffer;
|
||||
var writeAllHeaders = require('./all-headers').writeToTrackingBuffer;
|
||||
|
||||
/*
|
||||
s2.2.6.6
|
||||
*/
|
||||
module.exports = (function () {
|
||||
function SqlBatchPayload(sqlText, txnDescriptor, options) {
|
||||
_classCallCheck(this, SqlBatchPayload);
|
||||
|
||||
this.sqlText = sqlText;
|
||||
|
||||
var buffer = new WritableTrackingBuffer(100 + 2 * this.sqlText.length, 'ucs2');
|
||||
if (options.tdsVersion >= '7_2') {
|
||||
var outstandingRequestCount = 1;
|
||||
writeAllHeaders(buffer, txnDescriptor, outstandingRequestCount);
|
||||
}
|
||||
buffer.writeString(this.sqlText, 'ucs2');
|
||||
this.data = buffer.data;
|
||||
}
|
||||
|
||||
_createClass(SqlBatchPayload, [{
|
||||
key: 'toString',
|
||||
value: function toString(indent) {
|
||||
indent || (indent = '');
|
||||
return indent + ('SQL Batch - ' + this.sqlText);
|
||||
}
|
||||
}]);
|
||||
|
||||
return SqlBatchPayload;
|
||||
})();
|
||||
@@ -1,174 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var _classCallCheck = require('babel-runtime/helpers/class-call-check')['default'];
|
||||
|
||||
var _get = require('babel-runtime/helpers/get')['default'];
|
||||
|
||||
var _inherits = require('babel-runtime/helpers/inherits')['default'];
|
||||
|
||||
var _createClass = require('babel-runtime/helpers/create-class')['default'];
|
||||
|
||||
var _Object$keys = require('babel-runtime/core-js/object/keys')['default'];
|
||||
|
||||
var stream = require('readable-stream');
|
||||
var BufferList = require('bl');
|
||||
|
||||
var Job = function Job(length, execute) {
|
||||
_classCallCheck(this, Job);
|
||||
|
||||
this.length = length;
|
||||
this.execute = execute;
|
||||
}
|
||||
|
||||
// These jobs are non-dynamic, so we can reuse the job objects.
|
||||
// This should reduce GC pressure a bit (as less objects will be
|
||||
// created and garbage collected during stream parsing).
|
||||
;
|
||||
|
||||
var JOBS = {
|
||||
readInt8: new Job(1, function (buffer, offset) {
|
||||
return buffer.readInt8(offset);
|
||||
}),
|
||||
readUInt8: new Job(1, function (buffer, offset) {
|
||||
return buffer.readUInt8(offset);
|
||||
}),
|
||||
readInt16LE: new Job(2, function (buffer, offset) {
|
||||
return buffer.readInt16LE(offset);
|
||||
}),
|
||||
readInt16BE: new Job(2, function (buffer, offset) {
|
||||
return buffer.readInt16BE(offset);
|
||||
}),
|
||||
readUInt16LE: new Job(2, function (buffer, offset) {
|
||||
return buffer.readUInt16LE(offset);
|
||||
}),
|
||||
readUInt16BE: new Job(2, function (buffer, offset) {
|
||||
return buffer.readUInt16BE(offset);
|
||||
}),
|
||||
readInt32LE: new Job(4, function (buffer, offset) {
|
||||
return buffer.readInt32LE(offset);
|
||||
}),
|
||||
readInt32BE: new Job(4, function (buffer, offset) {
|
||||
return buffer.readInt32BE(offset);
|
||||
}),
|
||||
readUInt32LE: new Job(4, function (buffer, offset) {
|
||||
return buffer.readUInt32LE(offset);
|
||||
}),
|
||||
readUInt32BE: new Job(4, function (buffer, offset) {
|
||||
return buffer.readUInt32BE(offset);
|
||||
}),
|
||||
readInt64LE: new Job(8, function (buffer, offset) {
|
||||
return Math.pow(2, 32) * buffer.readInt32LE(offset + 4) + (buffer[offset + 4] & 0x80 === 0x80 ? 1 : -1) * buffer.readUInt32LE(offset);
|
||||
}),
|
||||
readInt64BE: new Job(8, function (buffer, offset) {
|
||||
return Math.pow(2, 32) * buffer.readInt32BE(offset) + (buffer[offset] & 0x80 === 0x80 ? 1 : -1) * buffer.readUInt32BE(offset + 4);
|
||||
}),
|
||||
readUInt64LE: new Job(8, function (buffer, offset) {
|
||||
return Math.pow(2, 32) * buffer.readUInt32LE(offset + 4) + buffer.readUInt32LE(offset);
|
||||
}),
|
||||
readUInt64BE: new Job(8, function (buffer, offset) {
|
||||
return Math.pow(2, 32) * buffer.readUInt32BE(offset) + buffer.readUInt32BE(offset + 4);
|
||||
}),
|
||||
readFloatLE: new Job(4, function (buffer, offset) {
|
||||
return buffer.readFloatLE(offset);
|
||||
}),
|
||||
readFloatBE: new Job(4, function (buffer, offset) {
|
||||
return buffer.readFloatBE(offset);
|
||||
}),
|
||||
readDoubleLE: new Job(8, function (buffer, offset) {
|
||||
return buffer.readDoubleLE(offset);
|
||||
}),
|
||||
readDoubleBE: new Job(8, function (buffer, offset) {
|
||||
return buffer.readDoubleBE(offset);
|
||||
})
|
||||
};
|
||||
|
||||
var StreamParser = (function (_stream$Transform) {
|
||||
_inherits(StreamParser, _stream$Transform);
|
||||
|
||||
function StreamParser(options) {
|
||||
_classCallCheck(this, StreamParser);
|
||||
|
||||
options = options || {};
|
||||
|
||||
if (options.objectMode === undefined) {
|
||||
options.objectMode = true;
|
||||
}
|
||||
|
||||
_get(Object.getPrototypeOf(StreamParser.prototype), 'constructor', this).call(this, options);
|
||||
|
||||
this.buffer = new BufferList();
|
||||
this.generator = undefined;
|
||||
this.currentStep = undefined;
|
||||
}
|
||||
|
||||
_createClass(StreamParser, [{
|
||||
key: 'parser',
|
||||
value: function parser() {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
}, {
|
||||
key: '_transform',
|
||||
value: function _transform(input, encoding, done) {
|
||||
this.buffer.append(input);
|
||||
|
||||
if (!this.generator) {
|
||||
this.generator = this.parser();
|
||||
this.currentStep = this.generator.next();
|
||||
}
|
||||
|
||||
var offset = 0;
|
||||
while (!this.currentStep.done) {
|
||||
var job = this.currentStep.value;
|
||||
if (!(job instanceof Job)) {
|
||||
return done(new Error('invalid job type'));
|
||||
}
|
||||
|
||||
var _length = job.length;
|
||||
if (this.buffer.length - offset < _length) {
|
||||
break;
|
||||
}
|
||||
|
||||
var result = job.execute(this.buffer, offset);
|
||||
offset += _length;
|
||||
this.currentStep = this.generator.next(result);
|
||||
}
|
||||
|
||||
this.buffer.consume(offset);
|
||||
|
||||
if (this.currentStep.done) {
|
||||
this.push(null);
|
||||
}
|
||||
|
||||
done();
|
||||
}
|
||||
}, {
|
||||
key: 'readBuffer',
|
||||
value: function readBuffer(length) {
|
||||
return new Job(length, function (buffer, offset) {
|
||||
return buffer.slice(offset, offset + length);
|
||||
});
|
||||
}
|
||||
}, {
|
||||
key: 'readString',
|
||||
value: function readString(length) {
|
||||
return new Job(length, function (buffer, offset) {
|
||||
return buffer.toString('utf8', offset, offset + length);
|
||||
});
|
||||
}
|
||||
}, {
|
||||
key: 'skip',
|
||||
value: function skip(length) {
|
||||
return new Job(length, function () {});
|
||||
}
|
||||
}]);
|
||||
|
||||
return StreamParser;
|
||||
})(stream.Transform);
|
||||
|
||||
module.exports = StreamParser;
|
||||
|
||||
_Object$keys(JOBS).forEach(function (jobName) {
|
||||
return StreamParser.prototype[jobName] = function () {
|
||||
return JOBS[jobName];
|
||||
};
|
||||
});
|
||||
@@ -1,15 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var versions = module.exports.versions = {
|
||||
'7_1': 0x71000001,
|
||||
'7_2': 0x72090002,
|
||||
'7_3_A': 0x730A0003,
|
||||
'7_3_B': 0x730B0003,
|
||||
'7_4': 0x74000004
|
||||
};
|
||||
|
||||
var versionsByValue = module.exports.versionsByValue = {};
|
||||
|
||||
for (var _name in versions) {
|
||||
versionsByValue[versions[_name]] = _name;
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
module.exports.BulkLoad = require('./bulk-load');
|
||||
module.exports.Connection = require('./connection');
|
||||
module.exports.Request = require('./request');
|
||||
module.exports.library = require('./library');
|
||||
|
||||
module.exports.TYPES = require('./data-type').typeByName;
|
||||
module.exports.ISOLATION_LEVEL = require('./transaction').ISOLATION_LEVEL;
|
||||
module.exports.TDS_VERSION = require('./tds-versions').versions;
|
||||
@@ -1,99 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var metadataParse = require('../metadata-parser');
|
||||
|
||||
function readTableName(parser, options, metadata, callback) {
|
||||
if (metadata.type.hasTableName) {
|
||||
if (options.tdsVersion >= '7_2') {
|
||||
parser.readUInt8(function (numberOfTableNameParts) {
|
||||
var tableName = [];
|
||||
|
||||
var i = 0;
|
||||
function next(done) {
|
||||
if (numberOfTableNameParts === i) {
|
||||
return done();
|
||||
}
|
||||
|
||||
parser.readUsVarChar(function (part) {
|
||||
tableName.push(part);
|
||||
|
||||
i++;
|
||||
|
||||
next(done);
|
||||
});
|
||||
}
|
||||
|
||||
next(function () {
|
||||
callback(tableName);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
parser.readUsVarChar(callback);
|
||||
}
|
||||
} else {
|
||||
callback(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
function readColumnName(parser, options, index, metadata, callback) {
|
||||
parser.readBVarChar(function (colName) {
|
||||
if (options.columnNameReplacer) {
|
||||
callback(options.columnNameReplacer(colName, index, metadata));
|
||||
} else if (options.camelCaseColumns) {
|
||||
callback(colName.replace(/^[A-Z]/, function (s) {
|
||||
return s.toLowerCase();
|
||||
}));
|
||||
} else {
|
||||
callback(colName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function readColumn(parser, options, index, callback) {
|
||||
metadataParse(parser, options, function (metadata) {
|
||||
readTableName(parser, options, metadata, function (tableName) {
|
||||
readColumnName(parser, options, index, metadata, function (colName) {
|
||||
callback({
|
||||
userType: metadata.userType,
|
||||
flags: metadata.flags,
|
||||
type: metadata.type,
|
||||
colName: colName,
|
||||
collation: metadata.collation,
|
||||
precision: metadata.precision,
|
||||
scale: metadata.scale,
|
||||
udtInfo: metadata.udtInfo,
|
||||
dataLength: metadata.dataLength,
|
||||
tableName: tableName
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = function (parser, colMetadata, options, callback) {
|
||||
parser.readUInt16LE(function (columnCount) {
|
||||
var columns = [];
|
||||
|
||||
var i = 0;
|
||||
function next(done) {
|
||||
if (i === columnCount) {
|
||||
return done();
|
||||
}
|
||||
|
||||
readColumn(parser, options, i, function (column) {
|
||||
columns.push(column);
|
||||
|
||||
i++;
|
||||
next(done);
|
||||
});
|
||||
}
|
||||
|
||||
next(function () {
|
||||
callback({
|
||||
name: 'COLMETADATA',
|
||||
event: 'columnMetadata',
|
||||
columns: columns
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||