#  Source: https://perlmonks.org/?node_id=1210595 
#  Author: Roboticushttps://perlmonks.org/?node_id=1210595

package PH;
use PadWalker qw/closed_over peek_sub set_closed_over/;
use Data::Dump qw/pp/;

# Set to an empty array to indicate that we're preparing a statement
my $inside_prepare = undef;

sub TIESCALAR {
    my ($class, $var) = @_;
    return bless \$var, $class;
}

# When we're preparing a statement, we'll return '?' for a tied value,
# and push a reference to the variable on the inside_prepare vector
# so the values and placeholders line up
sub FETCH {
    my $self = shift;
    return $$self unless defined $inside_prepare;
    push @$inside_prepare, $self;
    return "?";
}

sub STORE {
    my ($self,$new_val) = @_;
    $$self = $new_val;
}

# Fake DBI prep-like statement
sub prep {
   # Tell FETCH() that we're prepping a statement
   $inside_prepare = [];

   # call the sub to make perl do the string interpolation and
   # build the vector of variable references that align with
   # the placeholders
   my $t = shift;
    
   my $h_vars = closed_over($t);
#   warn pp $h_vars;
   tie ${$h_vars->{'$c2'}}, 'PH';
#   warn pp $h_vars;
    
   my $retval = [ $t->(), $inside_prepare ];

   # Turn off preparation mode
   $inside_prepare = undef;

   print "PREPARED: $retval->[0]\n";
   return $retval;
}

# Fake DBI execute-like statement
sub exec {
   my $x = shift;
   print "EXEC(", join(", ", map { $$_ } @{$x->[1]}), ")\n";
}

1; 



package main;


#!env perl
#
#   ex_sql_placeholders_via_tied_variables.pl
#
use strict;
use warnings;
#use PH;  # my fake placeholders package

# Tell PH which variables are to be converted into placeholders.
tie my $c1, 'PH';
my $c2='PH';

#tie my $c2, 'PH';
tie my $c3, 'PH';
tie my $c4, 'PH';

my $table='a_table';

sub the_sql { "select foo from $table where x=$c2 or y in ($c3, $c4, $c1)" };

# Prepare the statement
my $X = PH::prep(\&the_sql);

# Set up some values and execute:
($c2, $c3, $c4, $c1) = ('b','3','e','f');
PH::exec($X);

# Again...
($c1, $c2, $c3, $c4) = ('m','n','o','p','q');
PH::exec($X);

  
1;
