From 1be32ecd360e8ff71e55d9545b5e3549b0cab2aa Mon Sep 17 00:00:00 2001 From: Mark Carrington Date: Sat, 30 Oct 2021 14:51:11 +0100 Subject: [PATCH] Convert queries to M format for use in Power BI --- .../MarkMpn.Sql4Cds.SSMS.csproj | 2 + .../Resources/Sql2MCommand.png | Bin 0 -> 481 bytes MarkMpn.Sql4Cds.SSMS/Sql2MCommand.cs | 134 ++++++++++++++++ MarkMpn.Sql4Cds.SSMS/Sql4CdsPackage.cs | 1 + MarkMpn.Sql4Cds.SSMS/Sql4CdsPackage.vsct | 19 ++- MarkMpn.Sql4Cds/FetchXmlControl.Designer.cs | 2 + MarkMpn.Sql4Cds/FetchXmlControl.cs | 2 +- MarkMpn.Sql4Cds/FetchXmlControl.resx | 25 +++ MarkMpn.Sql4Cds/MQueryControl.Designer.cs | 64 ++++++++ MarkMpn.Sql4Cds/MQueryControl.cs | 100 ++++++++++++ MarkMpn.Sql4Cds/MQueryControl.resx | 145 ++++++++++++++++++ MarkMpn.Sql4Cds/MarkMpn.SQL4CDS.nuspec | 4 +- MarkMpn.Sql4Cds/MarkMpn.Sql4Cds.csproj | 9 ++ MarkMpn.Sql4Cds/PluginControl.cs | 41 +++++ MarkMpn.Sql4Cds/PluginControl.designer.cs | 116 ++++++++------ MarkMpn.Sql4Cds/PluginControl.resx | 16 ++ .../Resources/ExecutionPlan_16x.ico | Bin 0 -> 1150 bytes MarkMpn.Sql4Cds/Resources/PowerBi.ico | Bin 0 -> 1150 bytes MarkMpn.Sql4Cds/Resources/PowerBi.png | Bin 0 -> 481 bytes MarkMpn.Sql4Cds/SqlQueryControl.cs | 1 + 20 files changed, 625 insertions(+), 56 deletions(-) create mode 100644 MarkMpn.Sql4Cds.SSMS/Resources/Sql2MCommand.png create mode 100644 MarkMpn.Sql4Cds.SSMS/Sql2MCommand.cs create mode 100644 MarkMpn.Sql4Cds/MQueryControl.Designer.cs create mode 100644 MarkMpn.Sql4Cds/MQueryControl.cs create mode 100644 MarkMpn.Sql4Cds/MQueryControl.resx create mode 100644 MarkMpn.Sql4Cds/Resources/ExecutionPlan_16x.ico create mode 100644 MarkMpn.Sql4Cds/Resources/PowerBi.ico create mode 100644 MarkMpn.Sql4Cds/Resources/PowerBi.png diff --git a/MarkMpn.Sql4Cds.SSMS/MarkMpn.Sql4Cds.SSMS.csproj b/MarkMpn.Sql4Cds.SSMS/MarkMpn.Sql4Cds.SSMS.csproj index fd513366..182dc92e 100644 --- a/MarkMpn.Sql4Cds.SSMS/MarkMpn.Sql4Cds.SSMS.csproj +++ b/MarkMpn.Sql4Cds.SSMS/MarkMpn.Sql4Cds.SSMS.csproj @@ -70,6 +70,7 @@ + @@ -85,6 +86,7 @@ + Menus.ctmenu diff --git a/MarkMpn.Sql4Cds.SSMS/Resources/Sql2MCommand.png b/MarkMpn.Sql4Cds.SSMS/Resources/Sql2MCommand.png new file mode 100644 index 0000000000000000000000000000000000000000..a3393927f5f40c0ab6ac5746e2466554bfda19ce GIT binary patch literal 481 zcmV<70UrK|P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0d`45K~y+TjZ@oA z#4r$Cpa&HGX@ELtfDiuop$BT?k+m z*@Hn*j+Y1aemDQ>56GYmCn6ry_Ic%~jlcev0ZAN%9dLr;ohWia-+ zz}T15Q^bQ~*1*K0(8)1+e95esPf&%i7-@tVm?FCYW^jxh#Kq-8H8oHWR*ONy+byNf zC(N8^^}3?xyO#Dy@?AK(2tPYeUN@kvsN1+f6!tV5e7cZNHd>O3sX-A+p>hZPI*T)H zgaC2C2v{q=JHU@?0u!p4BCSp*M zMTa<80+iYpal%^hYXlea3h_Zp5bP7C@pLmWR!kGLSp+eql12u + /// Command handler + /// + internal sealed class Sql2MCommand : CommandBase + { + /// + /// Command ID. + /// + public const int CommandId = 0x0300; + + /// + /// Command menu group (command set GUID). + /// + public static readonly Guid CommandSet = new Guid("fd809e45-c5a9-40cc-9f78-501dd3f71817"); + + /// + /// Initializes a new instance of the class. + /// Adds our command handlers for menu (commands must exist in the command table file) + /// + /// Owner package, not null. + /// Command service to add command to, not null. + private Sql2MCommand(Sql4CdsPackage package, OleMenuCommandService commandService, DTE2 dte) : base(package, dte) + { + commandService = commandService ?? throw new ArgumentNullException(nameof(commandService)); + + var menuCommandID = new CommandID(CommandSet, CommandId); + var menuItem = new OleMenuCommand(this.Execute, menuCommandID); + menuItem.BeforeQueryStatus += QueryStatus; + commandService.AddCommand(menuItem); + } + + private void QueryStatus(object sender, EventArgs e) + { + var menuItem = (OleMenuCommand)sender; + + if (ActiveDocument == null || ActiveDocument.Language != "SQL" || !IsDataverse()) + { + menuItem.Enabled = false; + return; + } + + menuItem.Enabled = true; + } + + /// + /// Gets the instance of the command. + /// + public static Sql2MCommand Instance + { + get; + private set; + } + + /// + /// Initializes the singleton instance of the command. + /// + /// Owner package, not null. + public static async Task InitializeAsync(Sql4CdsPackage package, DTE2 dte) + { + // Verify the current thread is the UI thread - the call to AddCommand in Sql2FetchXmlCommand's constructor requires + // the UI thread. + ThreadHelper.ThrowIfNotOnUIThread(); + + OleMenuCommandService commandService = await package.GetServiceAsync((typeof(IMenuCommandService))) as OleMenuCommandService; + Instance = new Sql2MCommand(package, commandService, dte); + } + + /// + /// This function is the callback used to execute the command when the menu item is clicked. + /// See the constructor to see how the menu item is associated with this function using + /// OleMenuCommandService service and MenuCommand class. + /// + /// Event sender. + /// Event args. + private void Execute(object sender, EventArgs e) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + var scriptFactory = new ScriptFactoryWrapper(ServiceCache.ScriptFactory); + var sqlScriptEditorControl = scriptFactory.GetCurrentlyActiveFrameDocView(ServiceCache.VSMonitorSelection, false, out _); + + var constr = GetConnectionInfo(); + var server = constr.DataSource.Split(',')[0]; + + _ai.TrackEvent("Convert", new Dictionary { ["QueryType"] = "M", ["Source"] = "SSMS" }); + + var sql = GetQuery(); + var m = $@"/* +Query converted to M format by SQL 4 CDS +To use in Power BI: +1. Click New Source +2. Click Blank Query +3. Click Advanced Editor +4. Copy & paste in this query +*/ + +let + Source = CommonDataService.Database(""{server}"") + DataverseSQL = Value.NativeQuery(Source, ""{sql.Replace("\"", "\"\"").Replace("\r\n", " ").Trim()}"", null, [EnableFolding=true]) +in + DataverseSQL"; + + var window = Dte.ItemOperations.NewFile("General\\Text File"); + + var editPoint = ActiveDocument.EndPoint.CreateEditPoint(); + editPoint.Insert(m); + } + + private IEnumerable GetAllNodes(IExecutionPlanNode node) + { + foreach (var source in node.GetSources()) + { + yield return source; + + foreach (var subSource in GetAllNodes(source)) + yield return subSource; + } + } + } +} diff --git a/MarkMpn.Sql4Cds.SSMS/Sql4CdsPackage.cs b/MarkMpn.Sql4Cds.SSMS/Sql4CdsPackage.cs index c3cb6b78..faf4ebaf 100644 --- a/MarkMpn.Sql4Cds.SSMS/Sql4CdsPackage.cs +++ b/MarkMpn.Sql4Cds.SSMS/Sql4CdsPackage.cs @@ -69,6 +69,7 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke var dte = (DTE2)await GetServiceAsync(typeof(EnvDTE.DTE)); await Sql2FetchXmlCommand.InitializeAsync(this, dte); await FetchXml2SqlCommand.InitializeAsync(this, dte); + await Sql2MCommand.InitializeAsync(this, dte); // Intercept query execution DmlExecute.Initialize(this, dte); diff --git a/MarkMpn.Sql4Cds.SSMS/Sql4CdsPackage.vsct b/MarkMpn.Sql4Cds.SSMS/Sql4CdsPackage.vsct index 36c93dad..43d4982e 100644 --- a/MarkMpn.Sql4Cds.SSMS/Sql4CdsPackage.vsct +++ b/MarkMpn.Sql4Cds.SSMS/Sql4CdsPackage.vsct @@ -83,6 +83,17 @@ Convert FetchXML to SQL + @@ -94,6 +105,7 @@ must be the actual index (1-based) of the bitmap inside the bitmap strip. --> + @@ -107,14 +119,19 @@ + - + + + + + diff --git a/MarkMpn.Sql4Cds/FetchXmlControl.Designer.cs b/MarkMpn.Sql4Cds/FetchXmlControl.Designer.cs index 254c6069..f41591c9 100644 --- a/MarkMpn.Sql4Cds/FetchXmlControl.Designer.cs +++ b/MarkMpn.Sql4Cds/FetchXmlControl.Designer.cs @@ -29,6 +29,7 @@ protected override void Dispose(bool disposing) /// private void InitializeComponent() { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FetchXmlControl)); this.scintilla = new ScintillaNET.Scintilla(); this.SuspendLayout(); // @@ -48,6 +49,7 @@ private void InitializeComponent() this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(800, 450); this.Controls.Add(this.scintilla); + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); this.Name = "FetchXmlControl"; this.Text = "FetchXML Query"; this.ResumeLayout(false); diff --git a/MarkMpn.Sql4Cds/FetchXmlControl.cs b/MarkMpn.Sql4Cds/FetchXmlControl.cs index 942412f2..d150e9a7 100644 --- a/MarkMpn.Sql4Cds/FetchXmlControl.cs +++ b/MarkMpn.Sql4Cds/FetchXmlControl.cs @@ -35,7 +35,7 @@ public FetchXmlControl() { InitializeComponent(); - Text = $"* FetchXML {++_queryCounter}"; + Text = $"FetchXML {++_queryCounter} *"; // Ref: https://gist.github.com/anonymous/63036aa8c1cefcfcb013 diff --git a/MarkMpn.Sql4Cds/FetchXmlControl.resx b/MarkMpn.Sql4Cds/FetchXmlControl.resx index 1af7de15..98d4bddf 100644 --- a/MarkMpn.Sql4Cds/FetchXmlControl.resx +++ b/MarkMpn.Sql4Cds/FetchXmlControl.resx @@ -117,4 +117,29 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + AAABAAEAEBAAAAAAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAAMMOAADDDgAAAAAAAAAA + AAD29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA9vb2/0JCQv9CQkL/QkJC/0JCQv9CQkL/9vb2/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAPb29v9CQkL/8e/w//Hv8P/x7/D/QkJC//b29v8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAD29vb/QkJC//Hv8P/x7/D/8e/w/0JCQv/29vb/AAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA9vb2/0JCQv9CQkL/8e/w/0JCQv9CQkL/9vb2/wAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAPb29v/29vb/QkJC/0JCQv9CQkL/9vb2//b29v8AAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD29vYf9vb2//b29v9CQkL/9vb2//b29v/29vYQAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9vb2Afb29h/29vb/QkJC//b29v/29vYfAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPb29v/29vb/9vb2//b29v/29vb/9vb2//b2 + 9v/29vb/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD29vb/QkJC/0JCQv9CQkL/QkJC/0JC + Qv9CQkL/9vb2/wAAAAD29vYQ9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2/0JCQv/x7/D/8e/w//Hv + 8P/x7/D/QkJC//b29v/29vYQ9vb2//b29v9CQkL/QkJC/0JCQv9CQkL/9vb2//b29v9CQkL/8e/w//Hv + 8P/x7/D/8e/w/0JCQv/29vb/9vb2//b29v9CQkL/QkJC//Hv8P/x7/D/QkJC//b29v/29vb/QkJC//Hv + 8P/x7/D/8e/w//Hv8P9CQkL/9vb2/0JCQv9CQkL/QkJC//Hv8P/x7/D/8e/w/0JCQv/29vb/9vb2/0JC + Qv/x7/D/8e/w//Hv8P/x7/D/QkJC//b29v/29vb/9vb2/0JCQv9CQkL/8e/w//Hv8P9CQkL/9vb2//b2 + 9v9CQkL/QkJC/0JCQv9CQkL/QkJC/0JCQv/29vb/9vb2H/b29v/29vb/QkJC/0JCQv9CQkL/QkJC//b2 + 9v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b29gH29vYf9vb2//b29v/29vb/9vb2//b2 + 9v/29vb/Af8AAAH/AAAB/wAAAf8AAAH/AAAB/wAAAf8AAAP/AAAA/wAAAIAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAA== + + \ No newline at end of file diff --git a/MarkMpn.Sql4Cds/MQueryControl.Designer.cs b/MarkMpn.Sql4Cds/MQueryControl.Designer.cs new file mode 100644 index 00000000..0f6101ac --- /dev/null +++ b/MarkMpn.Sql4Cds/MQueryControl.Designer.cs @@ -0,0 +1,64 @@ + +namespace MarkMpn.Sql4Cds +{ + partial class MQueryControl + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MQueryControl)); + this.scintilla = new ScintillaNET.Scintilla(); + this.SuspendLayout(); + // + // scintilla + // + this.scintilla.Dock = System.Windows.Forms.DockStyle.Fill; + this.scintilla.Location = new System.Drawing.Point(0, 0); + this.scintilla.Margin = new System.Windows.Forms.Padding(2); + this.scintilla.Name = "scintilla"; + this.scintilla.ReadOnly = true; + this.scintilla.Size = new System.Drawing.Size(400, 234); + this.scintilla.TabIndex = 0; + // + // MQueryControl + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(400, 234); + this.Controls.Add(this.scintilla); + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.Margin = new System.Windows.Forms.Padding(2); + this.Name = "MQueryControl"; + this.Text = "M Query (Power BI)"; + this.ResumeLayout(false); + + } + + #endregion + + private ScintillaNET.Scintilla scintilla; + } +} \ No newline at end of file diff --git a/MarkMpn.Sql4Cds/MQueryControl.cs b/MarkMpn.Sql4Cds/MQueryControl.cs new file mode 100644 index 00000000..5b3750bb --- /dev/null +++ b/MarkMpn.Sql4Cds/MQueryControl.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using System.Xml; +using MarkMpn.Sql4Cds.Engine.ExecutionPlan; +using McTools.Xrm.Connection; +using ScintillaNET; + +namespace MarkMpn.Sql4Cds +{ + public partial class MQueryControl : WeifenLuo.WinFormsUI.Docking.DockContent, IDocumentWindow + { + private static int _queryCounter; + + public MQueryControl() + { + InitializeComponent(); + + Text = $"M Query (Power BI) {++_queryCounter} *"; + + // Ref: https://github.com/jacobslusser/ScintillaNET/wiki/Automatic-Syntax-Highlighting#complete-recipe + + // Configuring the default style with properties + // we have common to every lexer style saves time. + scintilla.StyleResetDefault(); + scintilla.Styles[Style.Default].Font = "Consolas"; + scintilla.Styles[Style.Default].Size = 10; + scintilla.StyleClearAll(); + + // Configure the CPP (C#) lexer styles + scintilla.Styles[Style.Cpp.Default].ForeColor = Color.Silver; + scintilla.Styles[Style.Cpp.Comment].ForeColor = Color.FromArgb(0, 128, 0); // Green + scintilla.Styles[Style.Cpp.CommentLine].ForeColor = Color.FromArgb(0, 128, 0); // Green + scintilla.Styles[Style.Cpp.CommentLineDoc].ForeColor = Color.FromArgb(128, 128, 128); // Gray + scintilla.Styles[Style.Cpp.Number].ForeColor = Color.Olive; + scintilla.Styles[Style.Cpp.Word].ForeColor = Color.Blue; + scintilla.Styles[Style.Cpp.Word2].ForeColor = Color.Blue; + scintilla.Styles[Style.Cpp.String].ForeColor = Color.FromArgb(163, 21, 21); // Red + scintilla.Styles[Style.Cpp.Character].ForeColor = Color.FromArgb(163, 21, 21); // Red + scintilla.Styles[Style.Cpp.Verbatim].ForeColor = Color.FromArgb(163, 21, 21); // Red + scintilla.Styles[Style.Cpp.StringEol].BackColor = Color.Pink; + scintilla.Styles[Style.Cpp.Operator].ForeColor = Color.Purple; + scintilla.Styles[Style.Cpp.Preprocessor].ForeColor = Color.Maroon; + scintilla.Lexer = Lexer.Cpp; + + // Set the keywords + scintilla.SetKeywords(0, "let in"); + } + + public string M + { + get { return scintilla.Text; } + set + { + scintilla.ReadOnly = false; + scintilla.Text = value; + scintilla.ReadOnly = true; + scintilla.Focus(); + } + } + + public void SetFocus() + { + scintilla.Focus(); + } + + void IDocumentWindow.Format() + { + + } + + void IDocumentWindow.Save() + { + throw new NotImplementedException(); + } + + TabContent IDocumentWindow.GetSessionDetails() + { + return new TabContent + { + Type = "M", + Query = scintilla.Text + }; + } + + void IDocumentWindow.RestoreSessionDetails(TabContent tab) + { + scintilla.ReadOnly = false; + scintilla.Text = tab.Query; + scintilla.ReadOnly = true; + } + } +} diff --git a/MarkMpn.Sql4Cds/MQueryControl.resx b/MarkMpn.Sql4Cds/MQueryControl.resx new file mode 100644 index 00000000..7343e83f --- /dev/null +++ b/MarkMpn.Sql4Cds/MQueryControl.resx @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + AAABAAEAEBAAAAAAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAAMMOAADDDgAAAAAAAAAA + AAAAAAAAAAAAAEXT9ec/0fT/OtD0/zXO9P8xzfT/F7Xq/wCc4P8AmN//AITP/wBXsv8AVrX/AFS05gAA + AAAAAAAAAAAAAAAAAABQ1vb/S9X1/0bT9f9A0vT/O9D0/x267P8BouP/AJ/i/wCL0v8AX7b/AF65/wBa + t/8AAAAAAAAAAAAAAAAAAAAAWtn2/1bY9v9R1vb/TNX2/0fU9f8nv+3/CKnl/wSl5P8BktX/AGa6/wBm + vv8AYrz/AAAAAAAAAAAAAAAAAAAAAGXc9/9g2/b/XNn2/1fY9v9S1/b/MMPv/xCv5/8MrOb/BpjY/wBu + v/8AbcL/AGrA/wAAAAAAAAAAAAAAAAAAAABv3vj/a933/2bc9/9i2/b/Xdr2/zrI8P8YtOn/FLLo/w2e + 2v8BdcL/AHTG/wBxxP8AAAAAAAAAAAAAAAAAAAAAeeH4/3Xg+P9w3/j/bN74/2fc9/9EzPH/Ibrr/xy3 + 6v8UpN3/BHvG/wN7yf8BeMj/AAAAAAAAAAAAAAAAAAAAAIPk+f9/4/j/euH4/3bg+P9y3/j/Ts/z/yq/ + 7f8lvOz/G6rf/weCyv8Fgs3/BH/L/wAAAAAAAAAAAAAAAAAAAACN5/rnieb5/4Tk+f+A4/j/e+L4/1HR + 8/8yxO//LsHu/yKv4v8KiM3/CIjQ/weFz/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARdDzv0LO + 8v8+y/H/O8nw/zfG8P8ptOT/DI7Q/wuO1P8Ji9L/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE3V + 9b9K0/T/RtHz/0LO8v8+zPH/MLrm/w+U1P8NlNf/DJHV/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABV2va/Udj2/07W9f9K0/T/RtHz/zbA6f8Qmtf/D5ra/w6X2P8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAXN74p1nd9/9V2vb/Udj2/07W9f82wuv/EaLd/xCg3f8Qndz/AAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAABOv44ATreL/E6vi/xKp4v8RpuH/EaTf/wAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUtumAFLTo/xOy5v8Tr+X/Eqzk/xKq4v8AAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFbvrgBW66v8Ut+n/FLXo/xOy5v8Tr+X/AAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABbA7WgVvu3/Fbzs/xW66v8Ut+n/FLXo5wAA + AAAAAAAAwAMAAMADAADAAwAAwAMAAMADAADAAwAAwAMAAMADAAD4AwAA+AMAAPgDAAD4AwAA/wMAAP8D + AAD/AwAA/wMAAA== + + + \ No newline at end of file diff --git a/MarkMpn.Sql4Cds/MarkMpn.SQL4CDS.nuspec b/MarkMpn.Sql4Cds/MarkMpn.SQL4CDS.nuspec index 683bfbf4..c9414828 100644 --- a/MarkMpn.Sql4Cds/MarkMpn.SQL4CDS.nuspec +++ b/MarkMpn.Sql4Cds/MarkMpn.SQL4CDS.nuspec @@ -22,9 +22,7 @@ plugins or integrations by writing familiar SQL and converting it. Using the preview TDS Endpoint, SELECT queries can also be run that aren't convertible to FetchXML. Convert SQL queries to FetchXML and execute them against Dataverse / D365 - Autocomplete is more responsive while metadata is loading in the background -Fixed datetime filtering with non-English regional settings -Enabled better integration from other tools + Convert queries to M format for use in Power BI Copyright © 2019 Mark Carrington en-GB diff --git a/MarkMpn.Sql4Cds/MarkMpn.Sql4Cds.csproj b/MarkMpn.Sql4Cds/MarkMpn.Sql4Cds.csproj index b0242aff..c11771ea 100644 --- a/MarkMpn.Sql4Cds/MarkMpn.Sql4Cds.csproj +++ b/MarkMpn.Sql4Cds/MarkMpn.Sql4Cds.csproj @@ -253,6 +253,12 @@ Component + + Form + + + MQueryControl.cs + Form @@ -310,6 +316,9 @@ + + MQueryControl.cs + FetchXmlControl.cs diff --git a/MarkMpn.Sql4Cds/PluginControl.cs b/MarkMpn.Sql4Cds/PluginControl.cs index 326813da..19ac26c1 100644 --- a/MarkMpn.Sql4Cds/PluginControl.cs +++ b/MarkMpn.Sql4Cds/PluginControl.cs @@ -132,6 +132,10 @@ private void PluginControl_Load(object sender, EventArgs e) query = CreateFetchXML(null); break; + case "M": + query = CreateM(null); + break; + default: continue; } @@ -214,6 +218,17 @@ private FetchXmlControl CreateFetchXML(string xml) return query; } + private MQueryControl CreateM(string m) + { + var query = new MQueryControl(); + query.M = m; + + query.Show(dockPanel, DockState.Document); + query.SetFocus(); + + return query; + } + private void tsbFormat_Click(object sender, EventArgs e) { if (dockPanel.ActiveDocument == null) @@ -386,11 +401,13 @@ private void SyncExecuteButton(object sender, EventArgs e) { tsbExecute.Enabled = false; tsbPreviewFetchXml.Enabled = false; + tsbPowerBi.Enabled = false; return; } tsbExecute.Enabled = query.Connection != null && !query.Busy; tsbPreviewFetchXml.Enabled = query.Connection != null && !query.Busy; + tsbPowerBi.Enabled = query.Connection != null && !query.Busy; } private void tsbStop_Click(object sender, EventArgs e) @@ -463,5 +480,29 @@ private void tsbFetchXMLBuilder_Click(object sender, EventArgs e) _ai.TrackEvent("Outgoing message", new Dictionary { ["TargetPlugin"] = args.TargetPlugin, ["Source"] = "XrmToolBox" }); OnOutgoingMessage(this, args); } + + private void tsbPowerBi_Click(object sender, EventArgs e) + { + if (!(dockPanel.ActiveDocument is SqlQueryControl sql)) + return; + + _ai.TrackEvent("Convert", new Dictionary { ["QueryType"] = "M", ["Source"] = "XrmToolBox" }); + + var m = $@"/* +Query converted to M format by SQL 4 CDS +To use in Power BI: +1. Click New Source +2. Click Blank Query +3. Click Advanced Editor +4. Copy & paste in this query +*/ + +let + Source = CommonDataService.Database(""{new Uri(sql.Connection.OriginalUrl).Host}"") + DataverseSQL = Value.NativeQuery(Source, ""{sql.Sql.Replace("\"", "\"\"").Replace("\r\n", " ").Trim()}"", null, [EnableFolding=true]) +in + DataverseSQL"; + CreateM(m); + } } } \ No newline at end of file diff --git a/MarkMpn.Sql4Cds/PluginControl.designer.cs b/MarkMpn.Sql4Cds/PluginControl.designer.cs index ee107bab..84788be4 100644 --- a/MarkMpn.Sql4Cds/PluginControl.designer.cs +++ b/MarkMpn.Sql4Cds/PluginControl.designer.cs @@ -31,25 +31,26 @@ private void InitializeComponent() this.components = new System.ComponentModel.Container(); System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(PluginControl)); this.toolStripMenu = new System.Windows.Forms.ToolStrip(); + this.toolStripSeparator = new System.Windows.Forms.ToolStripSeparator(); + this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator(); + this.toolStripSeparator3 = new System.Windows.Forms.ToolStripSeparator(); + this.tssSeparator1 = new System.Windows.Forms.ToolStripSeparator(); + this.dockPanel = new WeifenLuo.WinFormsUI.Docking.DockPanel(); + this.saveSessionTimer = new System.Windows.Forms.Timer(this.components); this.tsbConnect = new System.Windows.Forms.ToolStripButton(); this.tsbChangeConnection = new System.Windows.Forms.ToolStripButton(); - this.toolStripSeparator = new System.Windows.Forms.ToolStripSeparator(); this.tsbNewQuery = new System.Windows.Forms.ToolStripButton(); this.tsbOpen = new System.Windows.Forms.ToolStripButton(); this.tsbSave = new System.Windows.Forms.ToolStripButton(); - this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator(); this.tsbExecute = new System.Windows.Forms.ToolStripButton(); this.tsbStop = new System.Windows.Forms.ToolStripButton(); this.tsbPreviewFetchXml = new System.Windows.Forms.ToolStripButton(); - this.toolStripSeparator3 = new System.Windows.Forms.ToolStripSeparator(); + this.tsbFetchXMLBuilder = new System.Windows.Forms.ToolStripButton(); + this.tsbPowerBi = new System.Windows.Forms.ToolStripButton(); this.tsbIncludeFetchXml = new System.Windows.Forms.ToolStripButton(); - this.tssSeparator1 = new System.Windows.Forms.ToolStripSeparator(); this.tsbFormat = new System.Windows.Forms.ToolStripButton(); this.tsbSettings = new System.Windows.Forms.ToolStripButton(); this.tslAboutLink = new System.Windows.Forms.ToolStripLabel(); - this.dockPanel = new WeifenLuo.WinFormsUI.Docking.DockPanel(); - this.saveSessionTimer = new System.Windows.Forms.Timer(this.components); - this.tsbFetchXMLBuilder = new System.Windows.Forms.ToolStripButton(); this.toolStripMenu.SuspendLayout(); this.SuspendLayout(); // @@ -67,6 +68,7 @@ private void InitializeComponent() this.tsbStop, this.tsbPreviewFetchXml, this.tsbFetchXMLBuilder, + this.tsbPowerBi, this.toolStripSeparator3, this.tsbIncludeFetchXml, this.tssSeparator1, @@ -80,6 +82,41 @@ private void InitializeComponent() this.toolStripMenu.TabIndex = 4; this.toolStripMenu.Text = "toolStrip1"; // + // toolStripSeparator + // + this.toolStripSeparator.Name = "toolStripSeparator"; + this.toolStripSeparator.Size = new System.Drawing.Size(6, 25); + // + // toolStripSeparator2 + // + this.toolStripSeparator2.Name = "toolStripSeparator2"; + this.toolStripSeparator2.Size = new System.Drawing.Size(6, 25); + // + // toolStripSeparator3 + // + this.toolStripSeparator3.Name = "toolStripSeparator3"; + this.toolStripSeparator3.Size = new System.Drawing.Size(6, 25); + // + // tssSeparator1 + // + this.tssSeparator1.Name = "tssSeparator1"; + this.tssSeparator1.Size = new System.Drawing.Size(6, 25); + // + // dockPanel + // + this.dockPanel.Dock = System.Windows.Forms.DockStyle.Fill; + this.dockPanel.DocumentStyle = WeifenLuo.WinFormsUI.Docking.DocumentStyle.DockingWindow; + this.dockPanel.Location = new System.Drawing.Point(0, 25); + this.dockPanel.Name = "dockPanel"; + this.dockPanel.Size = new System.Drawing.Size(861, 478); + this.dockPanel.TabIndex = 5; + this.dockPanel.ActiveDocumentChanged += new System.EventHandler(this.dockPanel_ActiveDocumentChanged); + // + // saveSessionTimer + // + this.saveSessionTimer.Interval = 60000; + this.saveSessionTimer.Tick += new System.EventHandler(this.saveSessionTimer_Tick); + // // tsbConnect // this.tsbConnect.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; @@ -103,11 +140,6 @@ private void InitializeComponent() this.tsbChangeConnection.Text = "Change Connection"; this.tsbChangeConnection.Click += new System.EventHandler(this.tsbChangeConnection_Click); // - // toolStripSeparator - // - this.toolStripSeparator.Name = "toolStripSeparator"; - this.toolStripSeparator.Size = new System.Drawing.Size(6, 25); - // // tsbNewQuery // this.tsbNewQuery.Image = ((System.Drawing.Image)(resources.GetObject("tsbNewQuery.Image"))); @@ -141,11 +173,6 @@ private void InitializeComponent() this.tsbSave.ToolTipText = "Save File (Ctrl+S)"; this.tsbSave.Click += new System.EventHandler(this.tsbSave_Click); // - // toolStripSeparator2 - // - this.toolStripSeparator2.Name = "toolStripSeparator2"; - this.toolStripSeparator2.Size = new System.Drawing.Size(6, 25); - // // tsbExecute // this.tsbExecute.Enabled = false; @@ -180,10 +207,27 @@ private void InitializeComponent() this.tsbPreviewFetchXml.ToolTipText = "Display FetchXML Without Executing Query (Ctrl+L)"; this.tsbPreviewFetchXml.Click += new System.EventHandler(this.tsbPreviewFetchXml_Click); // - // toolStripSeparator3 + // tsbFetchXMLBuilder // - this.toolStripSeparator3.Name = "toolStripSeparator3"; - this.toolStripSeparator3.Size = new System.Drawing.Size(6, 25); + this.tsbFetchXMLBuilder.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.tsbFetchXMLBuilder.Enabled = false; + this.tsbFetchXMLBuilder.Image = global::MarkMpn.Sql4Cds.Properties.Resources.FXB; + this.tsbFetchXMLBuilder.ImageTransparentColor = System.Drawing.Color.Magenta; + this.tsbFetchXMLBuilder.Name = "tsbFetchXMLBuilder"; + this.tsbFetchXMLBuilder.Size = new System.Drawing.Size(23, 22); + this.tsbFetchXMLBuilder.Text = "Edit in FetchXML Builder"; + this.tsbFetchXMLBuilder.Click += new System.EventHandler(this.tsbFetchXMLBuilder_Click); + // + // tsbPowerBi + // + this.tsbPowerBi.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.tsbPowerBi.Enabled = false; + this.tsbPowerBi.Image = ((System.Drawing.Image)(resources.GetObject("tsbPowerBi.Image"))); + this.tsbPowerBi.ImageTransparentColor = System.Drawing.Color.Magenta; + this.tsbPowerBi.Name = "tsbPowerBi"; + this.tsbPowerBi.Size = new System.Drawing.Size(23, 22); + this.tsbPowerBi.Text = "toolStripButton1"; + this.tsbPowerBi.Click += new System.EventHandler(this.tsbPowerBi_Click); // // tsbIncludeFetchXml // @@ -197,11 +241,6 @@ private void InitializeComponent() this.tsbIncludeFetchXml.ToolTipText = "Display FetchXML when executing query (Ctrl+M)"; this.tsbIncludeFetchXml.Click += new System.EventHandler(this.tsbIncludeFetchXml_Click); // - // tssSeparator1 - // - this.tssSeparator1.Name = "tssSeparator1"; - this.tssSeparator1.Size = new System.Drawing.Size(6, 25); - // // tsbFormat // this.tsbFormat.Enabled = false; @@ -233,32 +272,6 @@ private void InitializeComponent() this.tslAboutLink.ToolTipText = "Documentation"; this.tslAboutLink.Click += new System.EventHandler(this.tslAboutLink_Click); // - // dockPanel - // - this.dockPanel.Dock = System.Windows.Forms.DockStyle.Fill; - this.dockPanel.DocumentStyle = WeifenLuo.WinFormsUI.Docking.DocumentStyle.DockingWindow; - this.dockPanel.Location = new System.Drawing.Point(0, 25); - this.dockPanel.Name = "dockPanel"; - this.dockPanel.Size = new System.Drawing.Size(861, 478); - this.dockPanel.TabIndex = 5; - this.dockPanel.ActiveDocumentChanged += new System.EventHandler(this.dockPanel_ActiveDocumentChanged); - // - // saveSessionTimer - // - this.saveSessionTimer.Interval = 60000; - this.saveSessionTimer.Tick += new System.EventHandler(this.saveSessionTimer_Tick); - // - // tsbFetchXMLBuilder - // - this.tsbFetchXMLBuilder.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; - this.tsbFetchXMLBuilder.Enabled = false; - this.tsbFetchXMLBuilder.Image = global::MarkMpn.Sql4Cds.Properties.Resources.FXB; - this.tsbFetchXMLBuilder.ImageTransparentColor = System.Drawing.Color.Magenta; - this.tsbFetchXMLBuilder.Name = "tsbFetchXMLBuilder"; - this.tsbFetchXMLBuilder.Size = new System.Drawing.Size(23, 22); - this.tsbFetchXMLBuilder.Text = "Edit in FetchXML Builder"; - this.tsbFetchXMLBuilder.Click += new System.EventHandler(this.tsbFetchXMLBuilder_Click); - // // PluginControl // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); @@ -297,5 +310,6 @@ private void InitializeComponent() private System.Windows.Forms.ToolStripButton tsbChangeConnection; private System.Windows.Forms.Timer saveSessionTimer; private System.Windows.Forms.ToolStripButton tsbFetchXMLBuilder; + private System.Windows.Forms.ToolStripButton tsbPowerBi; } } diff --git a/MarkMpn.Sql4Cds/PluginControl.resx b/MarkMpn.Sql4Cds/PluginControl.resx index 20a46a61..3bf484d9 100644 --- a/MarkMpn.Sql4Cds/PluginControl.resx +++ b/MarkMpn.Sql4Cds/PluginControl.resx @@ -193,6 +193,22 @@ ZrG2hLEVZn/rYPMAKAM/Pa8Cy9X7ZGoHDwvgCXYTNaME+4IyKcFuomaUYLd5Dq9xEsrv0ywAHxU/VfnI Dqd5AP5eDMAa7eNpHoAwAEqX8uosbb5SDqB0+TiAMivBvsLrE9PHhifYV3h9Yvq8Dbw+H+ut62ZhOcN1 81H/fABSBnEPJn8TgPBA92BymgCeYN8fdZgS7Bc7EcIbFPRwMzSLTHoAAAAASUVORK5CYII= + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAJdSURBVDhPjZJbSJNhGMffkaXVzB0rqC4KIuoiLMhAwSLS + oIO0SCU7ImTRuciovHCYgREJhZBIJ8QKzRiIU1ribMnQTLfpNnVtzh3c3OZy5rdv367+vZSjC8fWAw/v + xcPvx/vwf0iyCqpXXwx2SzHTJYVfJYWvUwKPUuRdGCevwGepPAZOKyncJsaUQoyFcfLyKcXy6fZ/oLtV + BGeL6P8FLoVY7vwohvMD7WYRHO+EsDcJEwsitpPvI7YTYC2FYMdkCJsLENBkwdYohO2NANaXgsSC8Hih + OgYyIwcxb8iHrycLow0CWBoyMPZ8VRKB6Yg6Bv4a2oe577nwqDJhrMvAaF06TE/5iQVz+jz1H3AgF6H+ + HIS0u+Fu3wp9LR/G2pUYfrx8sSA69cQb9TxC1F0NziUH56ygP5Bhtncnfn7ZDqdiEwZrVkBfkwbdw9Q4 + AvcDClaCc9wHN3kHnP0W5nUFCKq30SPaDHvLOnyrSsVg1VIMVKYsFnCOexQsp+ANcBNXELFeoLvvR0C1 + Ef7O9bA1SaCtSEF/xRL03eXFEUxcR8R2mYJlYH+U0ghPI9SXDV/7Wky3SWB5lQ5NOQ/a2wS9N8liQcR6 + noLnwI6fotkXgx09htmvmfAqBPC08jFWvwzqawSaqwQ9l+II2PESChaBNcvAmg4jbDxA99+CqeY0uN+m + wPSMB1UZQTftrtJ4AvNRhE2HEB7JBzO8F4w+BzOqDXA18uB8TWh0BB1nCT6dIegsiSMIG/K8jGEPBbPB + DO0CM7gDfuUaTL4gsNcT6KoJ2ooJlEX0PU48fylCfgOVdazE0jDYIAAAAABJRU5ErkJggg== diff --git a/MarkMpn.Sql4Cds/Resources/ExecutionPlan_16x.ico b/MarkMpn.Sql4Cds/Resources/ExecutionPlan_16x.ico new file mode 100644 index 0000000000000000000000000000000000000000..27d6794d459d1e5c3f1da488934de96f21a0d34d GIT binary patch literal 1150 zcmcgqK@P$o5QOO2aPn%8zJu5J4FBeX)C<=-vvvqUwy}wkW;(#`>=d>kL_8@4j4539 zAsjI~1KgT`Xm3mqyB*%Z%0?+=_Mn_KrZ(sN0q(Eq6qEjWf6M~w{aa5@`s*xP{B?#_ zyFq_qu4-rwwvOtaJVsswtcLE5YcIdwN3MB$+~@ae>U8d&8Dl;;dG)1N@5Rkos&-WW z3w^y?>Ie1h`?W8Yf9t6jS@B=_w?3LLcV@knk7BL=lIQiY{vV^fhz@z!v3tZ7aK;NE CUoH#) literal 0 HcmV?d00001 diff --git a/MarkMpn.Sql4Cds/Resources/PowerBi.ico b/MarkMpn.Sql4Cds/Resources/PowerBi.ico new file mode 100644 index 0000000000000000000000000000000000000000..89df1edf51a37219d164d842deb27ce6f935597a GIT binary patch literal 1150 zcma)*dq`7Z7{=e>N-HnZZ5C8uL3H^eEXY8iBGRCXWR#NF1tF8}MMkwh9A;odLLns5 z4Y5Kq1~L_wxTPYRsq-?m<#n5z*VNpO$NqVGHKf^K_#B?Y_nwdEci?;9k4VCgOh&9Q zN|h3Y@@gA<{!3g`IBIesrrm`DZB9fnXQOT$l6))ZjRo|i4RpQ?bglxFSdRbm ziQfgpcMFJP&M}jK!yPVc)la~$a0t?OHb|bEKq)$Q6)3sVBY&}H78ia9NMcR`b53>% z2(OuhOyhumu^lUOyFu6Wplj8j%Lb49jXn`qdS{WsoMh&lH4BKWpTaur2oxp52zYIV zB#pCo>Oi+@J@WSlMWhXgxY;k_Dzh#z=g_xlY|@QkL-{Ba1-+1}>foblfaG4iNB*I0 z4(V1A514b8Ik%Y;+cJY4d;k; zu5|{xYbUYu%TH|9Sg<;)34U2d_&(xZdQr4-Mt`vR7xpzyBdTEvd+VmKU2B7Xwh?Qx zJFsfLmtJ(-Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0d`45K~y+TjZ@oA z#4r$Cpa&HGX@ELtfDiuop$BT?k+m z*@Hn*j+Y1aemDQ>56GYmCn6ry_Ic%~jlcev0ZAN%9dLr;ohWia-+ zz}T15Q^bQ~*1*K0(8)1+e95esPf&%i7-@tVm?FCYW^jxh#Kq-8H8oHWR*ONy+byNf zC(N8^^}3?xyO#Dy@?AK(2tPYeUN@kvsN1+f6!tV5e7cZNHd>O3sX-A+p>hZPI*T)H zgaC2C2v{q=JHU@?0u!p4BCSp*M zMTa<80+iYpal%^hYXlea3h_Zp5bP7C@pLmWR!kGLSp+eql12u _con; + public string Sql => String.IsNullOrEmpty(_editor.SelectedText) ? _editor.Text : _editor.SelectedText; internal void ChangeConnection(ConnectionDetail con, SharedMetadataCache metadata, ITableSizeCache tableSize) {