Skip to content

Commit

Permalink
initial implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
plicease committed Oct 4, 2024
1 parent 9455403 commit b546905
Show file tree
Hide file tree
Showing 9 changed files with 294 additions and 5 deletions.
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
SQL-Formatter-*
/.build/
*.swp


/ffi/target
/ffi/_build
/ffi/Cargo.lock
70 changes: 70 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# SQL::Formatter ![static](https://github.com/uperl/SQL-Formatter/workflows/static/badge.svg) ![linux](https://github.com/uperl/SQL-Formatter/workflows/linux/badge.svg)

Format SQL using the rust sqlformat library

# SYNOPSIS

```perl
my $f = SQL::Formatter->new;
say $f->format('select foo.a, foo.b, bar.c from foo join bar on foo.a = bar.c where foo.b = 2');
```

prints:

```
SELECT
foo.a,
foo.b,
bar.c
FROM
foo
JOIN bar ON foo.a = bar.c
WHERE
foo.b = 2
```

# DESCRIPTION

Pretty print SQL using the rust crate `sqlformat`.

# ATTRIBUTES

The formatting options can be specified either when the object is constructed, or later using accessors.

```perl
my $f = SQL::Format->new( indent => 4 );
$f->indent(4);
```

- indent

Controls the length of indentation to use. The default is `2`.

- uppercase

When set to true (the default), changes reserved keywords to ALL CAPS.

- lines\_between\_queries

Controls the number of line breaks after a query. The default is `1`.

# METHODS

## format

```perl
my $pretty_sql = $f->format($sql);
```

Formats whitespace in a SQL string to make it easier to read.

# AUTHOR

Graham Ollis <plicease@cpan.org>

# COPYRIGHT AND LICENSE

This software is copyright (c) 2024 by Graham Ollis.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
3 changes: 2 additions & 1 deletion author.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ pod_spelling_system:
# (regardless of what spell check thinks)
# or stuff that I like to spell incorrectly
# intentionally
stopwords: []
stopwords:
- sqlformat

pod_coverage:
skip: 0
Expand Down
5 changes: 5 additions & 0 deletions dist.ini
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,9 @@ version_plugin = PkgVersion::Block
[Author::Plicease::Upload]
cpan = 0

[FFI::Build]
lang = Rust
build = Cargo

[PruneFiles]
filename = ffi/Cargo.lock
10 changes: 10 additions & 0 deletions ffi/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "sf"
version = "0.1.0"
edition = "2021"

[dependencies]
sqlformat = "0.2.6"

[lib]
crate-type = ["cdylib"]
26 changes: 26 additions & 0 deletions ffi/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use std::ffi::CString;
use std::ffi::CStr;
use sqlformat::format;
use std::os::raw::c_char;

#[no_mangle]
pub fn sf_format(query: *const c_char, indent: u8, uppercase: bool, between: u8) -> *mut c_char {
let query = unsafe { CStr::from_ptr(query) };
let options = sqlformat::FormatOptions {
indent: sqlformat::Indent::Spaces(indent),
uppercase: uppercase,
lines_between_queries: between,
};
let query = format(query.to_str().unwrap(), &sqlformat::QueryParams::default(), options);

CString::new(query).unwrap().into_raw()
}

#[allow(non_snake_case)]
#[no_mangle]
pub fn sf__free(s: *mut c_char) {
if s.is_null() {
} else {
unsafe { drop(CString::from_raw(s)) };
}
}
76 changes: 75 additions & 1 deletion lib/SQL/Formatter.pm
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,81 @@ use experimental qw( postderef signatures );
package SQL::Formatter {

# ABSTRACT: Format SQL using the rust sqlformat library

=head1 SYNOPSIS
my $f = SQL::Formatter->new;
say $f->format('select foo.a, foo.b, bar.c from foo join bar on foo.a = bar.c where foo.b = 2');
prints:
SELECT
foo.a,
foo.b,
bar.c
FROM
foo
JOIN bar ON foo.a = bar.c
WHERE
foo.b = 2
=head1 DESCRIPTION
Pretty print SQL using the rust crate C<sqlformat>.
=head1 ATTRIBUTES
The formatting options can be specified either when the object is constructed, or later using accessors.
my $f = SQL::Format->new( indent => 4 );
$f->indent(4);
=over 4
=item indent
Controls the length of indentation to use. The default is C<2>.
=item uppercase
When set to true (the default), changes reserved keywords to ALL CAPS.
=item lines_between_queries
Controls the number of line breaks after a query. The default is C<1>.
=back
=head1 METHODS
=head2 format
my $pretty_sql = $f->format($sql);
Formats whitespace in a SQL string to make it easier to read.
=cut

use FFI::Platypus 2.00;
use Class::Tiny {
indent => 2,
uppercase => 1,
lines_between_queries => 1
};

my $ffi = FFI::Platypus->new( api => 2, lang => 'Rust' );
$ffi->bundle;
$ffi->mangler(sub ($name) { "sf_$name" });

$ffi->attach( _free => ['opaque'] );

$ffi->attach( format => ['string','u8','bool','u8'] => 'opaque' => sub ($xsub, $self, $sql) {
my $ptr = $xsub->($sql, $self->indent, $self->uppercase, $self->lines_between_queries);
my $str = $ffi->cast( 'opaque' => 'string', $ptr );
_free($ptr);
return $str;
});

}

1;

91 changes: 91 additions & 0 deletions t/00_diag.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
use Test2::V0 -no_srand => 1;
use Config;

eval { require 'Test/More.pm' };

# This .t file is generated.
# make changes instead to dist.ini

my %modules;
my $post_diag;

$modules{$_} = $_ for qw(
Class::Tiny
ExtUtils::MakeMaker
FFI::Build::File::Cargo
FFI::Build::MM
FFI::Platypus
FFI::Platypus::Lang::Rust
Test2::V0
);



my @modules = sort keys %modules;

sub spacer ()
{
diag '';
diag '';
diag '';
}

pass 'okay';

my $max = 1;
$max = $_ > $max ? $_ : $max for map { length $_ } @modules;
our $format = "%-${max}s %s";

spacer;

my @keys = sort grep /(MOJO|PERL|\A(LC|HARNESS)_|\A(SHELL|LANG)\Z)/i, keys %ENV;

if(@keys > 0)
{
diag "$_=$ENV{$_}" for @keys;

if($ENV{PERL5LIB})
{
spacer;
diag "PERL5LIB path";
diag $_ for split $Config{path_sep}, $ENV{PERL5LIB};

}
elsif($ENV{PERLLIB})
{
spacer;
diag "PERLLIB path";
diag $_ for split $Config{path_sep}, $ENV{PERLLIB};
}

spacer;
}

diag sprintf $format, 'perl', "$] $^O $Config{archname}";

foreach my $module (sort @modules)
{
my $pm = "$module.pm";
$pm =~ s{::}{/}g;
if(eval { require $pm; 1 })
{
my $ver = eval { $module->VERSION };
$ver = 'undef' unless defined $ver;
diag sprintf $format, $module, $ver;
}
else
{
diag sprintf $format, $module, '-';
}
}

if($post_diag)
{
spacer;
$post_diag->();
}

spacer;

done_testing;

13 changes: 12 additions & 1 deletion t/sql_formatter.t
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
use Test2::V0 -no_srand => 1;
use SQL::Formatter;

ok 1, 'todo';
my $f = SQL::Formatter->new;

my @ret;
is(
[@ret=$f->format("select x, y, z from foo join bar where x = 1 and y = 2")],
[D()],
'does something at least'
);

note @ret;

note $f->format('select foo.a, foo.b, bar.c from foo join bar on foo.a = bar.c where foo.b = 2');

done_testing;

Expand Down

0 comments on commit b546905

Please sign in to comment.